A JavaScript development environment for Emacs https://indium.readthedocs.io
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

182 lines
7.0 KiB

  1. ;;; indium-script.el --- Handle scripts for a connection -*- lexical-binding: t; -*-
  2. ;; Copyright (C) 2017 Nicolas Petton
  3. ;; Author: Nicolas Petton <nicolas@petton.fr>
  4. ;; This program is free software; you can redistribute it and/or modify
  5. ;; it under the terms of the GNU General Public License as published by
  6. ;; the Free Software Foundation, either version 3 of the License, or
  7. ;; (at your option) any later version.
  8. ;; This program is distributed in the hope that it will be useful,
  9. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. ;; GNU General Public License for more details.
  12. ;; You should have received a copy of the GNU General Public License
  13. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. ;;; Commentary:
  15. ;; Handle script source registration, script locations (with sourcemap support)
  16. ;; for the current indium connection.
  17. ;;; Code:
  18. (require 'seq)
  19. (require 'indium-backend)
  20. (require 'indium-structs)
  21. (require 'indium-workspace)
  22. (require 'indium-backend)
  23. (require 'sourcemap)
  24. (require 'memoize)
  25. (defgroup indium-script nil
  26. "Indium script and location handling"
  27. :prefix "indium-script-"
  28. :group 'indium)
  29. (defcustom indium-script-enable-sourcemaps t
  30. "When non-nil, use sourcemaps when debugging."
  31. :type 'boolean)
  32. (defun indium-location-url (location)
  33. "Lookup the url associated with LOCATION's file."
  34. (indium-workspace-make-url (indium-location-file location)))
  35. (defun indium-script-add-script-parsed (id url &optional sourcemap-url)
  36. "Add a parsed script from the runtime with ID at URL.
  37. If SOURCEMAP-URL is non-nil, add it to the parsed script."
  38. (map-put (indium-current-connection-scripts)
  39. (intern id)
  40. (make-indium-script :id id
  41. :url url
  42. :sourcemap-url sourcemap-url)))
  43. (defun indium-script-find-by-id (id)
  44. "Return the parsed script with id ID in the current connection.
  45. If not such script was parsed, return nil."
  46. (map-elt (indium-current-connection-scripts) (intern id)))
  47. (defun indium-script-get-file (script)
  48. "Lookup the local file associated with SCRIPT.
  49. If no local file can be found, return nil."
  50. (indium-workspace-lookup-file (indium-script-url script)))
  51. (defun indium-script-find-from-url (url)
  52. "Lookup a script for URL.
  53. Return nil if no script can be found."
  54. (seq-find #'identity
  55. (map-apply (lambda (_id script)
  56. (when (string= url (indium-script-url script))
  57. script))
  58. (indium-current-connection-scripts))))
  59. (defun indium-script-find-from-file (file)
  60. "Lookup a script from a local FILE.
  61. Return nil if no script can be found."
  62. (indium-script-find-from-url (indium-workspace-make-url file)))
  63. (defun indium-script-has-sourcemap-p (script)
  64. "Return non-nil if SCRIPT has an associated sourcemap."
  65. (when-let ((sourcemap-url (indium-script-sourcemap-url script)))
  66. (not (seq-empty-p sourcemap-url))))
  67. (defun indium-script-all-scripts-with-sourcemap ()
  68. "Return all parsed scripts that contain a sourcemap."
  69. (seq-filter #'indium-script-has-sourcemap-p
  70. (map-values (indium-current-connection-scripts))))
  71. (defun indium-script-sourcemap-file (script)
  72. "Return the local sourcemap file associated with SCRIPT.
  73. If no sourcemap file can be found, return nil."
  74. (when (indium-script-has-sourcemap-p script)
  75. (when-let ((script-file (indium-script-get-file script)))
  76. (indium-workspace-lookup-file-safe
  77. (expand-file-name (indium-script-sourcemap-url script)
  78. (file-name-directory script-file))))))
  79. (defun indium-script-get-frame-original-location (frame)
  80. "Return the location stack FRAME, possibly using sourcemaps."
  81. (let* ((script (indium-frame-script frame))
  82. (location (indium-frame-location frame)))
  83. (if (indium-location-file location)
  84. (progn
  85. (indium-script-original-location script location))
  86. location)))
  87. (defun indium-script-original-location (script location)
  88. "Use the sourcemap of SCRIPT to lookup its original LOCATION.
  89. If SCRIPT has no sourcemap, return LOCATION."
  90. (if indium-script-enable-sourcemaps
  91. (if-let ((sourcemap-file (indium-script-sourcemap-file script))
  92. (sourcemap (sourcemap-from-file sourcemap-file))
  93. (original-location (indium-script--sourcemap-original-position-for
  94. sourcemap
  95. :line (1+ (indium-location-line location))
  96. :column (1+ (indium-location-column location))
  97. :nearest t))
  98. (file (expand-file-name (plist-get original-location :source)
  99. (file-name-directory (indium-script-get-file script)))))
  100. (make-indium-location :file file
  101. :line (max 0 (1- (plist-get original-location :line)))
  102. :column (plist-get original-location :column))
  103. (progn
  104. (message "The sourcemap file does not exist!")
  105. location))
  106. location))
  107. (defun indium-script-generated-location (location)
  108. "Return a generated location from the original LOCATION.
  109. If there is a parsed script for LOCATION's file, return LOCATION.
  110. Otherwise, if a sourcemap exists, generate a location using that
  111. sourcemap."
  112. (let ((file (indium-location-file location)))
  113. (if (indium-script-find-from-file file)
  114. location
  115. (if indium-script-enable-sourcemaps
  116. (or (seq-some (lambda (script)
  117. (if-let ((sourcemap-file (indium-script-sourcemap-file script))
  118. (script-file (indium-script-get-file script))
  119. (sourcemap (sourcemap-from-file sourcemap-file))
  120. (generated-location (sourcemap-generated-position-for
  121. sourcemap
  122. :source (file-relative-name
  123. (indium-location-file location)
  124. (file-name-directory script-file))
  125. :line (1+ (indium-location-line location))
  126. :column 0
  127. :nearest t)))
  128. (make-indium-location :file script-file
  129. :line (max 0 (1- (plist-get generated-location :line)))
  130. :column (plist-get generated-location :column))))
  131. (indium-script-all-scripts-with-sourcemap))
  132. location)
  133. location))))
  134. (defun indium-script-generated-location-at-point ()
  135. "Return a location for the position of POINT.
  136. If no location can be found, return nil."
  137. (indium-script-generated-location
  138. (make-indium-location :file buffer-file-name
  139. :line (1- (line-number-at-pos)))))
  140. ;; TODO: wait for https://github.com/syohex/emacs-sourcemap/pull/6 to be merged
  141. (defun indium-script--sourcemap-original-position-for (sourcemap &rest props)
  142. "Lookup a position in SOURCEMAP based on PROPS.
  143. PROPS should be a plist with a `:line' and `:column' key."
  144. (let ((here (make-sourcemap-entry :generated-line (plist-get props :line)
  145. :generated-column (plist-get props :column))))
  146. (let ((ret (sourcemap--binary-search sourcemap here 'generated
  147. (plist-get props :nearest))))
  148. (when ret
  149. (list :source (sourcemap-entry-source ret)
  150. :line (sourcemap-entry-original-line ret)
  151. :column (sourcemap-entry-original-column ret))))))
  152. (memoize 'indium-script-original-location "2 minutes")
  153. (provide 'indium-script)
  154. ;;; indium-script.el ends here