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.

228 lines
8.5 KiB

4 years ago
  1. ;;; indium-workspace.el --- Use local files for debugging -*- lexical-binding: t; -*-
  2. ;; Copyright (C) 2017 Nicolas Petton
  3. ;; Author: Nicolas Petton <nicolas@petton.fr>
  4. ;; Keywords:
  5. ;; This program is free software; you can redistribute it and/or modify
  6. ;; it under the terms of the GNU General Public License as published by
  7. ;; the Free Software Foundation, either version 3 of the License, or
  8. ;; (at your option) any later version.
  9. ;; This program is distributed in the hope that it will be useful,
  10. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. ;; GNU General Public License for more details.
  13. ;; You should have received a copy of the GNU General Public License
  14. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. ;;; Commentary:
  16. ;; Setup a workspace for using local files when debugging JavaScript.
  17. ;;
  18. ;; TODO: make it work with nodejs
  19. ;;
  20. ;; Files are looked up using a special `.indium' file placed in the root directory
  21. ;; of the files served.
  22. ;;
  23. ;;; Example:
  24. ;; With the following directory structure:
  25. ;;
  26. ;; project/ (current directory)
  27. ;; www/
  28. ;; index.html
  29. ;; css/
  30. ;; style.css
  31. ;; js/
  32. ;; app.js
  33. ;; .indium
  34. ;;
  35. ;; For the following URL "http://localhost:3000/js/app.js"
  36. ;; `indium-workspace-lookup-file' will return "./www/js/app.js".
  37. ;;
  38. ;; Previously used workspace directories are saved and stored in
  39. ;; "~/.emacs.d/indium-workspaces.el". You can change its location by
  40. ;; customizing `indium-workspace-file'.
  41. ;;
  42. ;; To disable persisting the list of workspaces to a file, you can set
  43. ;; `indium-workspace-use-workspace-file' to `nil'. In this case,
  44. ;; `indium-workspaces' can be manually set in your emacs.d.
  45. ;;
  46. ;;; Code:
  47. (require 'url)
  48. (require 'seq)
  49. (require 'map)
  50. (require 'subr-x)
  51. (require 'indium-structs)
  52. (require 'indium-backend)
  53. (declare-function indium-repl-get-buffer "indium-repl.el")
  54. (defgroup indium-workspace nil
  55. "Indium workspace"
  56. :prefix "indium-worspace-"
  57. :group 'indium)
  58. (defcustom indium-workspace-file (locate-user-emacs-file "indium-workspaces.el")
  59. "Location of the file used to store previously used workspace directories."
  60. :type 'file)
  61. (defcustom indium-workspace-use-workspace-file t
  62. "Persist the list of worskpaces used in a file."
  63. :type 'boolean)
  64. (defvar indium-workspaces nil
  65. "List of previously used workspace directories.")
  66. (defun indium-workspace-read ()
  67. "Ask the user to select a workspace directory.
  68. If the current directory is within a workspace, simply return it
  69. without prompting the user.
  70. The selected workspace directory is added to the list of workspaces."
  71. (when indium-workspace-use-workspace-file
  72. (indium-workspace--read-workspaces-file))
  73. (let ((workspace (or (indium-workspace-root)
  74. (when (and indium-workspaces
  75. (y-or-n-p "No workspace found. Select one? "))
  76. (completing-read "Choose a workspace: " indium-workspaces)))))
  77. (when (and workspace indium-workspace-use-workspace-file)
  78. (indium-workspace--add-directory workspace)
  79. (indium-workspace--save-workspaces-file)
  80. workspace)))
  81. (defun indium-workspace-lookup-file (url)
  82. "Return a local file matching URL for the current connection.
  83. If no file is found, return nil."
  84. (when url
  85. (or (indium-workspace--lookup-using-file-protocol url)
  86. (indium-workspace--lookup-using-workspace url))))
  87. (defun indium-workspace-lookup-file-safe (url)
  88. "Find a local file for URL, or return URL is no file can be found."
  89. (or (indium-workspace-lookup-file url) url))
  90. (defun indium-workspace--lookup-using-file-protocol (url)
  91. "Return a local file matching URL if URL use the file:// protocol."
  92. (when (indium-workspace--file-protocol-p)
  93. (let* ((url (url-generic-parse-url url))
  94. (path (car (url-path-and-query url))))
  95. (when (file-regular-p path)
  96. path))))
  97. (defun indium-workspace--lookup-using-workspace (url)
  98. "Return a local file matching URL using the current Indium workspace."
  99. ;; Make sure we are in the correct directory so that indium can find a
  100. ;; ".indium" file.
  101. (with-current-buffer (indium-repl-get-buffer)
  102. (if-let ((root (indium-workspace-root)))
  103. (let* ((path (seq-drop (car (url-path-and-query
  104. (url-generic-parse-url url)))
  105. 1))
  106. (file (expand-file-name path root)))
  107. (when (file-regular-p file)
  108. file)))))
  109. (defun indium-workspace-make-url (file)
  110. "Return the url associated with the local FILE."
  111. (or (indium-workspace--make-url-using-file-path file)
  112. (indium-workspace--make-url-using-file-protocol file)
  113. (indium-workspace--make-url-using-workspace file)))
  114. (defun indium-workspace--make-url-using-file-path (file)
  115. "When using nodejs, the path of FILE should be used directly."
  116. (when (indium-connection-nodejs-p indium-current-connection)
  117. file))
  118. (defun indium-workspace--make-url-using-file-protocol (file)
  119. "Return a url using the \"file://\" protocol for FILE.
  120. If the current connection doesn't use the file protocol, return nil."
  121. (when (indium-workspace--file-protocol-p)
  122. (format "file://%s" file)))
  123. (defun indium-workspace--make-url-using-workspace (file)
  124. "Return the url associated with the local FILE.
  125. The url is built using `indium-workspace-root'."
  126. (if-let ((root (indium-workspace-root)))
  127. (let* ((url (indium-workspace--url-basepath (indium-current-connection-url)))
  128. (path (file-relative-name file root)))
  129. (setf (url-filename url) (indium-workspace--absolute-path path))
  130. (url-recreate-url url))))
  131. (defun indium-workspace--file-protocol-p ()
  132. "Return non-nil if the current connection use the file protocol."
  133. (let ((url (url-generic-parse-url (indium-current-connection-url))))
  134. (string= (url-type url) "file")))
  135. (defun indium-workspace--absolute-path (path)
  136. "Return PATH as absolute.
  137. Prepend a \"/\" to PATH unless it already starts with one."
  138. (unless (string= (seq-take path 1) "/")
  139. (concat "/" path)))
  140. (defun indium-workspace--url-basepath (url)
  141. "Return an urlobj with the basepath of URL.
  142. The path and query string of URL are stripped."
  143. (let ((urlobj (url-generic-parse-url url)))
  144. (url-parse-make-urlobj (url-type urlobj)
  145. (url-user urlobj)
  146. (url-password urlobj)
  147. (url-host urlobj)
  148. (url-port urlobj)
  149. nil nil nil t)))
  150. (defun indium-workspace-root ()
  151. "Lookup the root workspace directory from the current buffer."
  152. (indium-workspace-locate-dominating-file default-directory ".indium"))
  153. (defun indium-workspace-locate-dominating-file (file name)
  154. "Look up the directory hierarchy from FILE for a directory containing NAME.
  155. Stop at the first parent directory containing a file NAME,
  156. and return the directory. Return nil if not found.
  157. Instead of a string, NAME can also be a predicate taking one argument
  158. \(a directory) and returning a non-nil value if that directory is the one for
  159. which we're looking."
  160. ;; copied from projectile.el, itself copied from files.el (stripped comments)
  161. ;; emacs-24 bzr branch 2014-03-28 10:20
  162. (setq file (abbreviate-file-name file))
  163. (let ((root nil)
  164. try)
  165. (while (not (or root
  166. (null file)
  167. (string-match locate-dominating-stop-dir-regexp file)))
  168. (setq try (if (stringp name)
  169. (file-exists-p (expand-file-name name file))
  170. (funcall name file)))
  171. (cond (try (setq root file))
  172. ((equal file (setq file (file-name-directory
  173. (directory-file-name file))))
  174. (setq file nil))))
  175. (and root (expand-file-name (file-name-as-directory root)))))
  176. (defun indium-workspace--add-directory (directory)
  177. "Add DIRECTORY to the list of workspaces."
  178. (add-to-list 'indium-workspaces directory))
  179. (defun indium-workspace--save-workspaces-file ()
  180. "Save previously used workspace directories."
  181. (make-directory (file-name-directory indium-workspace-file) t)
  182. (with-temp-file indium-workspace-file
  183. (emacs-lisp-mode)
  184. (insert ";; This file is automatically generated by the Indium.")
  185. (newline)
  186. (insert (format "(setq %s '%S)" "indium-workspaces" indium-workspaces))))
  187. (defun indium-workspace--read-workspaces-file ()
  188. "Read the workspaces file and set `indium-worspaces'."
  189. (load indium-workspace-file t))
  190. (provide 'indium-workspace)
  191. ;;; indium-workspace.el ends here