Advertisement
Guest User

Untitled

a guest
Aug 18th, 2023
353
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lisp 10.81 KB | None | 0 0
  1. ;;; flutter.el --- Tools for working with Flutter SDK -*- lexical-binding: t -*-
  2.  
  3. ;; Copyright (C) 2018-2019 Aaron Madlon-Kay
  4.  
  5. ;; Author: Aaron Madlon-Kay
  6. ;; Version: 0.1.0
  7. ;; URL: https://github.com/amake/flutter.el
  8. ;; Package-Requires: ((emacs "25.1"))
  9. ;; Keywords: languages
  10.  
  11. ;; This file is not part of GNU Emacs.
  12.  
  13. ;; flutter.el is free software; you can redistribute it and/or modify it under
  14. ;; the terms of the GNU General Public License as published by the Free Software
  15. ;; Foundation; either version 3, or (at your option) any later version.
  16. ;;
  17. ;; flutter.el is distributed in the hope that it will be useful, but WITHOUT ANY
  18. ;; WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  19. ;; A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
  20. ;;
  21. ;; You should have received a copy of the GNU General Public License along with
  22. ;; flutter.el.  If not, see http://www.gnu.org/licenses.
  23.  
  24. ;;; Commentary:
  25.  
  26. ;; flutter.el is a package for running the `flutter' binary from the Flutter SDK
  27. ;; interactively.  It is most useful when paired with `dart-mode'.
  28.  
  29. ;;; Code:
  30.  
  31. (require 'comint)
  32. (require 'json)
  33. (require 'flutter-project)
  34. (require 'flutter-l10n)
  35.  
  36. (defconst flutter-buffer-name "*Flutter*")
  37.  
  38. (defvar flutter-sdk-path nil
  39.   "Path to Flutter SDK.")
  40.  
  41. ;;; Key bindings
  42.  
  43. (defconst flutter-interactive-keys-alist
  44.   '(("r" . hot-reload)
  45.     ("R" . hot-restart)
  46.     ("v" . open-devtools)
  47.     ("s" . screenshot)
  48.     ("w" . widget-hierarchy)
  49.     ("t" . rendering-tree)
  50.     ("L" . layers)
  51.     ("S" . accessibility-traversal-order)
  52.     ("U" . accessibility-inverse-hit-test-order)
  53.     ("i" . inspector)
  54.     ("p" . construction-lines)
  55.     ("I" . invert-oversized-images)
  56.     ("o" . operating-systems)
  57.     ("b" . brightness)
  58.     ("P" . performance-overlay)
  59.     ("a" . timeline-events)
  60.     ("M" . write-shaders)
  61.     ("g" . run-code-generators)
  62.     ("h" . help)
  63.     ("d" . detatch)
  64.     ("c" . clear-screen)
  65.     ("q" . quit)))
  66.  
  67. (defvar flutter-mode-map
  68.   (copy-keymap comint-mode-map)
  69.   "Basic mode map for `flutter-run'.")
  70.  
  71. (defvar flutter-test-mode-map
  72.   (let ((map (make-sparse-keymap)))
  73.     (define-key map (kbd "C-c C-t n")   'flutter-test-current-file)
  74.     (define-key map (kbd "C-c C-t C-n") 'flutter-test-current-file)
  75.     (define-key map (kbd "C-c C-t t")   'flutter-test-at-point)
  76.     (define-key map (kbd "C-c C-t C-t") 'flutter-test-at-point)
  77.     (define-key map (kbd "C-c C-t a")   'flutter-test-all)
  78.     (define-key map (kbd "C-c C-t C-a") 'flutter-test-all)
  79.     map)
  80.   "The keymap used in command `flutter-test-mode' buffers.")
  81.  
  82. (defun flutter--make-interactive-function (key name)
  83.   "Define a function that sends KEY to the `flutter` process.
  84. The function's name will be NAME prefixed with \"flutter-\"."
  85.   (let* ((name-str (symbol-name name))
  86.          (funcname (intern (concat "flutter-" name-str))))
  87.     (defalias funcname
  88.       `(lambda ()
  89.          ,(format "Send key '%s' to inferior flutter to invoke '%s' function." key name-str)
  90.          (interactive)
  91.          (flutter--send-command ,key)))))
  92.  
  93. (defun flutter-register-key (key name)
  94.   "Register a KEY with NAME recognized by the `flutter` process.
  95. A function `flutter-NAME' will be created that sends the key to
  96. the `flutter` process."
  97.   (let ((func (flutter--make-interactive-function key name)))
  98.     (define-key flutter-mode-map key func)))
  99.  
  100. (defun flutter-register-keys (key-alist)
  101.   "Call `flutter-register-key' on all (key . name) pairs in KEY-ALIST."
  102.   (dolist (item key-alist)
  103.     (flutter-register-key (car item) (cdr item))))
  104.  
  105. (defun flutter-hot-reload ()
  106.   "Dummy to suppress compiler warning.")
  107.  
  108. (flutter-register-keys flutter-interactive-keys-alist)
  109.  
  110. ;;; Internal utilities
  111.  
  112. (defmacro flutter--from-project-root (&rest body)
  113.   "Execute BODY with cwd set to the project root."
  114.   `(let ((root (flutter-project-get-root)))
  115.      (if root
  116.          (let ((default-directory root))
  117.            ,@body)
  118.        (error "Root of Flutter project not found"))))
  119.  
  120. (defmacro flutter--with-run-proc (args &rest body)
  121.   "Execute BODY while ensuring an inferior `flutter` process is running.
  122.  
  123. ARGS is a space-delimited string of CLI flags passed to
  124. `flutter`, and can be nil."
  125.   `(flutter--from-project-root
  126.     (let* ((buffer (flutter--get-buffer-create flutter-buffer-name))
  127.            (alive (flutter--running-p))
  128.            (arglist (if ,args (split-string ,args))))
  129.       (unless alive
  130.         (apply #'make-comint-in-buffer "Flutter" buffer (flutter-build-command) nil "run" arglist))
  131.       (with-current-buffer buffer
  132.         (unless (derived-mode-p 'flutter-mode)
  133.           (flutter-mode)))
  134.       ,@body)))
  135.  
  136. (defun flutter--get-buffer-create (buffer-or-name)
  137.   "Same as `get-buffer-create' but ensures BUFFER-OR-NAME has our CWD.
  138.  
  139. If the existing buffer's CWD doesn't match, kill it and recreate it."
  140.   (let* ((existing-buf (get-buffer buffer-or-name))
  141.          (existing-buf-cwd (when existing-buf
  142.                              (with-current-buffer existing-buf
  143.                                default-directory))))
  144.     (if (string= default-directory existing-buf-cwd)
  145.         existing-buf
  146.       (when existing-buf
  147.         (unless (kill-buffer existing-buf)
  148.           (error "Flutter already running in %s" existing-buf-cwd)))
  149.       (get-buffer-create buffer-or-name))))
  150.  
  151. (defun flutter--running-p ()
  152.   "Return non-nil if the `flutter` process is already running."
  153.   (comint-check-proc flutter-buffer-name))
  154.  
  155. (defun flutter--send-command (command)
  156.   "Send COMMAND to a running Flutter process."
  157.   (flutter--with-run-proc
  158.    nil
  159.    (let ((proc (get-buffer-process flutter-buffer-name)))
  160.      (comint-send-string proc command))))
  161.  
  162. (defun flutter--test (&rest args)
  163.   "Execute `flutter test` inside Emacs.
  164.  
  165. ARGS is a list of CLI flags passed to
  166. `flutter`, and can be nil."
  167.   (flutter--from-project-root
  168.    (compilation-start
  169.     (format "%s %s"
  170.             (flutter-build-test-command)
  171.             (mapconcat #'identity args " "))
  172.     t)))
  173.  
  174. ;; The second part of the regexp is a translation of this PCRE, which correctly
  175. ;; handles escaped quotes:
  176. ;;
  177. ;; (['\"])(.*?(?<!\\)(?:\\\\)*)\1,
  178. ;;
  179. ;; Emacs doesn't have negative lookbehind, so the above is reimplemented as:
  180. ;;
  181. ;; (['\"])(.*[^\\](?:\\\\)*|(?:\\\\)*)\1,
  182. ;;
  183. ;; This was then translated to the below with the pcre2el package:
  184. ;;
  185. ;; (rxt-pcre-to-elisp (read-string "regexp: "))
  186. (defconst flutter--test-case-regexp
  187.   (concat "^[ \t]*\\(?:testWidgets\\|test\\|group\\)([\n \t]*"
  188.           "\\([\"']\\)\\(.*[^\\]\\(?:\\\\\\\\\\)*\\|\\(?:\\\\\\\\\\)*\\)\\1,")
  189.   "Regexp for finding the string title of a test or test group.
  190. The title will be in match 2.")
  191.  
  192. (defun flutter--find-test-case (line)
  193.   "Search backwards for test name starting at LINE on current buffer."
  194.   (save-excursion
  195.     (goto-char (point-min))
  196.     (forward-line (1- line))
  197.     (end-of-line)
  198.     (if (re-search-backward flutter--test-case-regexp nil t)
  199.         (match-string 2))))
  200.  
  201. (defun flutter--initialize ()
  202.   "Helper function to initialize Flutter."
  203.   (setq comint-process-echoes nil))
  204.  
  205. (defun flutter--buffer-relative-file-name ()
  206.   "Return the current buffer's file name relative to project root."
  207.   (file-relative-name buffer-file-name (flutter-project-get-root)))
  208.  
  209. ;;; Public interface
  210.  
  211. (defun flutter-build-command ()
  212.   "Build flutter command to execute."
  213.   (let ((bin (when flutter-sdk-path
  214.                (concat (file-name-as-directory flutter-sdk-path) "bin/"))))
  215.     (concat (or bin "") "flutter")))
  216.  
  217. (defun flutter-build-test-command ()
  218.   "Build test command appropriate for the current buffer."
  219.   (let ((flutter (flutter-build-command)))
  220.     (cond ((flutter-file-p) (format "%s test" flutter))
  221.           ;; `flutter pub` is failing lately, so prefer "real" `pub`
  222.           ((executable-find "pub") "pub run test")
  223.           (t (format "%s pub run test" flutter)))))
  224.  
  225. ;;;###autoload
  226. (define-minor-mode flutter-test-mode
  227.   "Toggle Flutter-Test minor mode.
  228. With no argument, this command toggles the mode. Non-null prefix
  229. argument turns on the mode. Null prefix argument turns off the
  230. mode."
  231.   :init-value nil
  232.   :lighter " Flutter-Test"
  233.   :keymap 'flutter-test-mode-map
  234.   :group 'flutter-test)
  235.  
  236. (defun flutter-test-file-p ()
  237.   "Return non-nil if the current buffer appears to be a Flutter test file."
  238.   (save-excursion
  239.     (goto-char (point-min))
  240.     (re-search-forward "^import 'package:flutter_test/flutter_test.dart';" nil t)))
  241.  
  242. (defun flutter-file-p ()
  243.   "Return non-nil if the current buffer appears to be a Flutter file."
  244.   (save-excursion
  245.     (goto-char (point-min))
  246.     (re-search-forward "^import 'package:flutter\\(?:_test\\)?/.*';" nil t)))
  247.  
  248. ;;;###autoload
  249. (defun flutter-run (&optional args)
  250.   "Execute `flutter run` inside Emacs.
  251.  
  252. ARGS is a space-delimited string of CLI flags passed to
  253. `flutter`, and can be nil.  Call with a prefix to be prompted for
  254. args."
  255.   (interactive
  256.    (list (when current-prefix-arg
  257.            (read-string "Args: "))))
  258.   (flutter--with-run-proc
  259.    args
  260.    (display-buffer buffer)))
  261.  
  262. (defun flutter--devices ()
  263.   "Return an alist of devices in (name . ID) format."
  264.   (let* ((output (shell-command-to-string "flutter devices --machine"))
  265.          (vec (json-read-from-string output)))
  266.     (mapcar
  267.      (lambda (alist) (let-alist alist (cons .name .id)))
  268.      vec)))
  269.  
  270. ;;;###autoload
  271. (defun flutter-run-device (device-id)
  272.   "Start `flutter run` with DEVICE-ID."
  273.   (interactive
  274.    (list (let* ((collection (flutter--devices))
  275.                 (choice (completing-read "Device: " collection)))
  276.            (cdr (assoc choice collection)))))
  277.   (flutter-run (format "-d %s" device-id)))
  278.  
  279. ;;;###autoload
  280. (defun flutter-run-or-hot-reload ()
  281.   "Start `flutter run` or hot-reload if already running."
  282.   (interactive)
  283.   (if (flutter--running-p)
  284.       (flutter-hot-reload)
  285.     (flutter-run)))
  286.  
  287. ;;;###autoload
  288. (defun flutter-test-all ()
  289.   "Execute `flutter test` inside Emacs."
  290.   (interactive)
  291.   (flutter--test))
  292.  
  293. ;;;###autoload
  294. (defun flutter-test-current-file ()
  295.   "Execute `flutter test <current-file>` inside Emacs."
  296.   (interactive)
  297.   (flutter--test (flutter--buffer-relative-file-name)))
  298.  
  299. ;;;###autoload
  300. (defun flutter-test-at-point ()
  301.   "Execute `flutter test --plain-name <test-name-at-point> <current-file>`."
  302.   (interactive)
  303.   (let* ((test-file (flutter--buffer-relative-file-name))
  304.          (line (line-number-at-pos (point)))
  305.          (case (flutter--find-test-case line)))
  306.     (if case
  307.         (flutter--test "--plain-name" (format "'%s'" case) test-file)
  308.       (error "No test case found at point"))))
  309.  
  310. ;;;###autoload
  311. (define-derived-mode flutter-mode comint-mode "Flutter"
  312.   "Major mode for `flutter-run'.
  313.  
  314. \\{flutter-mode-map}"
  315.   (setq comint-prompt-read-only t))
  316.  
  317. (add-hook 'flutter-mode-hook #'flutter--initialize)
  318.  
  319. (provide 'flutter)
  320. ;;; flutter.el ends here
  321.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement