Browse Source

Initial support for sourcemaps

Extract script and location access to indium-script.el.
Add support for looking up original positions in indium-script.el.
workspaces
Nicolas Petton 5 years ago
parent
commit
630a4b13ca
Signed by: nico GPG Key ID: 233587A47C207910
12 changed files with 221 additions and 62 deletions
  1. +2
    -0
      Cask
  2. +2
    -2
      indium-backend.el
  3. +4
    -2
      indium-debugger-frames.el
  4. +11
    -16
      indium-debugger.el
  5. +136
    -0
      indium-script.el
  6. +6
    -28
      indium-webkit.el
  7. +7
    -2
      indium-workspace.el
  8. +1
    -1
      indium.el
  9. +1
    -0
      test/integration/indium-repl-integration-test.el
  10. +45
    -0
      test/unit/indium-script-test.el
  11. +0
    -11
      test/unit/indium-webkit-test.el
  12. +6
    -0
      test/unit/indium-workspace-test.el

+ 2
- 0
Cask View File

@ -5,6 +5,8 @@
(depends-on "websocket")
(depends-on "company")
(depends-on "sourcemap")
(depends-on "memoize")
(development
(depends-on "undercover")


+ 2
- 2
indium-backend.el View File

@ -224,8 +224,8 @@ prototype chain of the remote object.")
"Get the source of the script for FRAME.
Evaluate CALLBACK with the result.")
(cl-defgeneric indium-backend-get-script-url (backend frame)
"Return the url of the script for FRAME, or nil.")
(cl-defgeneric indium-backend-get-script (backend frame)
"Return the script for FRAME, or nil.")
(cl-defgeneric indium-backend-resume (backend &optional callback)
"Resume the debugger and evaluate CALLBACK if non-nil.")


+ 4
- 2
indium-debugger-frames.el View File

@ -24,6 +24,7 @@
;;; Code:
(require 'indium-render)
(require 'indium-script)
(declare 'indium-debugger-frames)
(declare 'indium-debugger-current-frame)
@ -60,8 +61,9 @@ CURRENT-FRAME is the current stack frame in the debugger."
(newline 2)
(seq-doseq (frame frames)
(indium-render-frame frame
(indium-backend-get-script-url (indium-backend) frame)
(eq current-frame frame))
(indium-script-get-url
(indium-backend-get-script (indium-backend) frame))
(eq current-frame frame))
(newline))))
(defun indium-debugger-frames-select-frame (frame)


+ 11
- 16
indium-debugger.el View File

@ -168,19 +168,15 @@ Try to find the file for the stack frame locally first using
Indium worskspaces. If not local file can be found, get the
remote source for that frame."
(indium-debugger-set-current-frame frame)
;; when a buffer is already debugging a frame, be sure to clean it first.
(if-let (old-buf (indium-debugger-get-buffer-create))
(with-current-buffer old-buf
(indium-debugger-unset-current-buffer)))
(indium-debugger-litable-setup-buffer)
(switch-to-buffer (indium-debugger-get-buffer-create))
(indium-debugger-litable-setup-buffer)
(if buffer-file-name
(indium-debugger-setup-buffer-with-file)
(indium-backend-get-script-source
(indium-backend)
frame
(lambda (source)
(indium-debugger-setup-buffer-no-file
(indium-debugger-setup-buffer-with-source
(map-nested-elt source '(result scriptSource)))))))
(defun indium-debugger-setup-buffer-with-file ()
@ -189,8 +185,8 @@ remote source for that frame."
(revert-buffer nil nil t))
(indium-debugger--goto-current-frame))
(defun indium-debugger-setup-buffer-no-file (source)
"Setup the current buffer with the frame source SOURCE."
(defun indium-debugger-setup-buffer-with-source (source)
"Setup the current buffer with the frame SOURCE."
(unless (string= (buffer-substring-no-properties (point-min) (point-max))
source)
(let ((inhibit-read-only t))
@ -201,7 +197,7 @@ remote source for that frame."
(defun indium-debugger--goto-current-frame ()
"Move the point to the current stack frame position in the current buffer."
(let* ((frame (indium-debugger-current-frame))
(location (map-elt frame 'location))
(location (indium-script-get-location frame))
(line (map-elt location 'lineNumber))
(column (map-elt location 'columnNumber)))
(goto-char (point-min))
@ -339,15 +335,13 @@ Evaluation happens in the context of the current call frame."
"Return all frames in the current stack."
(map-elt indium-connection 'frames))
;; TODO: remove
(defun indium-debugger-lookup-file ()
"Lookup the local file associated with the current connection.
Return nil if no local file can be found."
(let ((url (indium-backend-get-script-url (indium-backend)
(indium-debugger-current-frame))))
;; Make sure we are in the correct directory so that indium can find a ".indium"
;; file.
(with-current-buffer (indium-repl-get-buffer)
(indium-workspace-lookup-file url))))
(let ((script (indium-backend-get-script (indium-backend)
(indium-debugger-current-frame))))
(indium-workspace-lookup-file (indium-script-get-url script))))
(defun indium-debugger-get-current-scopes ()
"Return the scope of the current stack frame."
@ -377,7 +371,8 @@ CALLBACK is evaluated with two arguments, the properties and SCOPE."
"Create a debugger buffer for the current connection and return it.
If a buffer already exists, just return it."
(let ((buf (if-let ((file (indium-debugger-lookup-file)))
(let* ((location (indium-script-get-location (indium-debugger-current-frame)))
(buf (if-let ((file (map-elt location 'file)))
(find-file file)
(get-buffer-create (indium-debugger--buffer-name-no-file)))))
(indium-debugger-setup-buffer buf)


+ 136
- 0
indium-script.el View File

@ -0,0 +1,136 @@
;;; indium-script.el --- Handle scripts for a connection -*- lexical-binding: t; -*-
;; Copyright (C) 2017 Nicolas Petton
;; Author: Nicolas Petton <nicolas@petton.fr>
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; Handle script source registration, script locations (with sourcemap support)
;; for the current indium connection.
;;
;; Scripts are alists indexed by id in the current Indium connection. A script
;; contain an `url' key, and an optional `sourcemap-url' key.
;;
;; A location is an alist with a `lineNumber' and `columnNumber' key. If a
;; location points to a local file, it also contains a `file' key. Columns and
;; lines start at 0.
;;; Code:
(require 'seq)
(require 'indium-backend)
(require 'indium-workspace)
(require 'indium-backend)
(require 'sourcemap)
(require 'memoize)
(defun indium-script-add-script-parsed (id url &optional sourcemap-url)
"Add a parsed script from the runtime with ID at URL.
If SOURCEMAP-URL is non-nil, add it to the parsed script."
(unless (map-elt indium-connection 'scripts)
(map-put indium-connection 'scripts '()))
(map-put (map-elt indium-connection 'scripts)
(intern id)
`((url . ,url)
(sourcemap-url . ,sourcemap-url))))
(defun indium-script-get (id)
"Return the location for the script with id ID.
If not such script was parsed, return nil."
(map-elt (map-elt indium-connection 'scripts) (intern id)))
(defun indium-script-get-url (script)
"Return the url for SCRIPT."
(map-elt script 'url))
(defun indium-script-get-file (script)
"Lookup the local file associated with SCRIPT.
If no local file can be found, return nil."
(indium-workspace-lookup-file (indium-script-get-url script)))
(defun indium-script-get-id (url)
"Lookup the parsed script id for URL."
(seq-find #'identity
(map-apply (lambda (key script)
(when (string= url (map-elt script 'url))
(symbol-name key)))
(map-elt indium-connection 'scripts))))
(defun indium-script-has-sourcemap-p (script)
"Return non-nil if SCRIPT has an associated sourcemap."
(let ((sourcemap-url (map-elt script 'sourcemap-url)))
(and sourcemap-url (not (seq-empty-p sourcemap-url)))))
(defun indium-script-get-location (frame)
"Return the location stack FRAME.
The location is an list with `lineNumber' and `columnNumber'
keys. The location also contains a `file' key if a local file is
associated to the FRAME."
(let* ((script (indium-backend-get-script (indium-backend) frame))
(location (map-elt frame 'location))
(file (indium-workspace-lookup-file (indium-script-get-url script))))
(if file
(progn
(map-put location 'file file)
(indium-script-original-location script location))
location)))
(defun indium-script-original-location (script location)
"Use the sourcemap of SCRIPT to lookup its original LOCATION.
If SCRIPT has no sourcemap, return LOCATION. LOCATION is an
alist with the `lineNumber' and `columnNumber' keys."
(if (indium-script-has-sourcemap-p script)
(if-let ((script-file (indium-script-get-file script))
(sourcemap-file (indium-workspace-lookup-file-safe
(expand-file-name (map-elt script 'sourcemap-url)
(file-name-directory script-file)))))
(let* ((sourcemap (sourcemap-from-file sourcemap-file))
(original-location (indium-script--sourcemap-original-position-for
sourcemap
:line (1+ (map-elt location 'lineNumber))
:column (1+ (map-elt location 'columnNumber))
:nearest t)))
(if original-location
(let ((file (expand-file-name (plist-get original-location :source)
(file-name-directory script-file))))
`((file . ,file)
(lineNumber . ,(max 0 (1- (plist-get original-location :line))))
(columnNumber . ,(max 0 (1- (plist-get original-location :column))))))
(progn
(message "Could not locate original position from sourcemap!")
location)))
(progn
(message "The sourcemap file does not exist!")
location))
location))
;; TODO: wait for https://github.com/syohex/emacs-sourcemap/pull/6 to be merged
(defun indium-script--sourcemap-original-position-for (sourcemap &rest props)
(let ((here (make-sourcemap-entry :generated-line (plist-get props :line)
:generated-column (plist-get props :column))))
(let ((ret (sourcemap--binary-search sourcemap here 'generated
(plist-get props :nearest))))
(when ret
(list :source (sourcemap-entry-source ret)
:line (sourcemap-entry-original-line ret)
:column (sourcemap-entry-original-column ret))))))
(memoize 'indium-script-original-location "2 minutes")
(provide 'indium-script)
;;; indium-script.el ends here

+ 6
- 28
indium-webkit.el View File

@ -40,6 +40,7 @@
(require 'indium-repl)
(require 'indium-debugger)
(require 'indium-workspace)
(require 'indium-script)
(defvar indium-webkit-cache-disabled nil
"Network cache disabled state. If non-nil disable cache when Indium starts.")
@ -159,7 +160,7 @@ prototype chain of the remote object."
(map-nested-elt response '(result result)))))))
(cl-defmethod indium-backend-set-script-source ((_backend (eql webkit)) url source &optional callback)
(when-let ((script-id (indium-webkit--get-script-id url)))
(when-let ((script-id (indium-script-get-id url)))
(indium-webkit--send-request
`((method . "Runtime.compileScript")
(params . ((expression . ,source)
@ -181,9 +182,9 @@ prototype chain of the remote object."
(params . ((scriptId . ,script-id))))
callback)))
(cl-defmethod indium-backend-get-script-url ((_backend (eql webkit)) frame)
(cl-defmethod indium-backend-get-script ((_backend (eql webkit)) frame)
(let ((script-id (map-nested-elt frame '(location scriptId))))
(when script-id (indium-webkit--get-script-url script-id))))
(when script-id (indium-script-get script-id))))
(cl-defmethod indium-backend-resume ((_backend (eql webkit)) &optional callback)
"Resume the debugger and evaluate CALLBACK if non-nil."
@ -384,8 +385,8 @@ MESSAGE explains why the connection has been closed."
"Handle a script parsed event with MESSAGE."
(let* ((scriptId (map-nested-elt message '(params scriptId)))
(url (map-nested-elt message '(params url)))
(sourcemap-url (map-nested-elt message '(params sourceMapUrl))))
(indium-webkit--add-script-parsed scriptId url sourcemap-url)))
(sourcemap-url (map-nested-elt message '(params sourceMapURL))))
(indium-script-add-script-parsed scriptId url sourcemap-url)))
(defun indium-webkit--handle-ws-closed (_ws)
"Cleanup function called when the connection socket is closed."
@ -632,29 +633,6 @@ RESULT should be a reference to a remote object."
(callFrameId . ,(map-elt frame 'callFrameId))))
list))
(defun indium-webkit--add-script-parsed (scriptId url &optional sourcemap-url)
"Add a parsed script from the runtime with id SCRIPTID at URL.
If SOURCEMAP-URL is non-nil, add it to the parsed script."
(unless (map-elt indium-connection 'scripts)
(map-put indium-connection 'scripts '()))
(map-put (map-elt indium-connection 'scripts)
(intern scriptId)
`((url . ,url)
(sourcemap-url . ,sourcemap-url))))
(defun indium-webkit--get-script-url (scriptId)
"Lookup the parsed script with id SCRIPTID.
If no such script has been parsed, return nil."
(map-nested-elt indium-connection `(scripts ,(intern scriptId) url)))
(defun indium-webkit--get-script-id (url)
"Lookup the parsed script id for URL."
(seq-find #'identity
(map-apply (lambda (key script)
(when (string= url (map-elt script 'url))
(symbol-name key)))
(map-elt indium-connection 'scripts))))
(defvar indium-webkit--request-id 0)
(defun indium-webkit--next-request-id ()
"Return the next unique identifier to be used in a request."


+ 7
- 2
indium-workspace.el View File

@ -63,6 +63,8 @@
(require 'indium-backend)
(declare-function indium-repl-get-buffer "indium-repl.el")
(defgroup indium-workspace nil
"Indium workspace"
:prefix "indium-worspace-"
@ -117,13 +119,16 @@ If no file is found, return nil."
(defun indium-workspace--lookup-using-workspace (url)
"Return a local file matching URL using the current Indium workspace."
(if-let ((root (indium-workspace-root)))
;; Make sure we are in the correct directory so that indium can find a
;; ".indium" file.
(with-current-buffer (indium-repl-get-buffer)
(if-let ((root (indium-workspace-root)))
(let* ((path (seq-drop (car (url-path-and-query
(url-generic-parse-url url)))
1))
(file (expand-file-name path root)))
(when (file-regular-p file)
file))))
file)))))
(defun indium-workspace-make-url (file)
"Return the url associated with the local FILE."


+ 1
- 1
indium.el View File

@ -6,7 +6,7 @@
;; URL: https://github.com/NicolasPetton/indium
;; Keywords: tools, javascript
;; Version: 0.6.1
;; Package-Requires: ((emacs "25") (seq "2.16") (js2-mode "20140114") (company "0.9.0") (websocket "1.6"))
;; Package-Requires: ((emacs "25") (seq "2.16") (js2-mode "20140114") (company "0.9.0") (websocket "1.6") (memoize "1.0.1"))
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by


+ 1
- 0
test/integration/indium-repl-integration-test.el View File

@ -32,6 +32,7 @@
(require 'cl-lib)
(require 'indium-repl)
(require 'indium-nodejs)
(describe "Repl output"
(it "should display a prompt"


+ 45
- 0
test/unit/indium-script-test.el View File

@ -0,0 +1,45 @@
;;; indium-script-test.el --- Unit tests for indium-script.el -*- lexical-binding: t; -*-
;; Copyright (C) 2017 Nicolas Petton
;; Author: Nicolas Petton <nicolas@petton.fr>
;; Keywords: test
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(require 'buttercup)
(require 'indium-script)
(describe "Looking up scripts"
(it "should be able to retrieve parsed scripts url"
(with-fake-indium-connection
(indium-script-add-script-parsed "1" "foo")
(expect (map-elt (indium-script-get "1") 'url) :to-equal "foo")))
(it "should be able to retrieve parsed scripts sourcemap url"
(with-fake-indium-connection
(indium-script-add-script-parsed "1" "foo" "foo-map")
(expect (map-elt (indium-script-get "1") 'sourcemap-url) :to-equal "foo-map")))
(it "should be able to retrieve parsed scripts ids"
(with-fake-indium-connection
(indium-script-add-script-parsed "1" "foo")
(expect (indium-script-get-id "foo") :to-equal "1"))))
(provide 'indium-script-test)
;;; indium-script-test.el ends here

+ 0
- 11
test/unit/indium-webkit-test.el View File

@ -152,16 +152,5 @@
(properties . [((name . "a") (type . "boolean") (value . "true"))]))))
:to-equal "{ a: true }")))
(describe "Parsed scripts handling"
(it "should be able to retrieve parsed scripts urls"
(with-fake-indium-connection
(indium-webkit--add-script-parsed "1" "foo")
(expect (indium-webkit--get-script-url "1") :to-equal "foo")))
(it "should be able to retrieve parsed scripts ids"
(with-fake-indium-connection
(indium-webkit--add-script-parsed "1" "foo")
(expect (indium-webkit--get-script-id "foo") :to-equal "1"))))
(provide 'indium-webkit-test)
;;; indium-webkit-test.el ends here

+ 6
- 0
test/unit/indium-workspace-test.el View File

@ -87,33 +87,39 @@
(describe "Looking up files"
(it "cannot lookup file when no workspace it set"
(spy-on 'indium-workspace-root :and-return-value nil)
(spy-on 'indium-repl-get-buffer :and-return-value (current-buffer))
(expect (indium-workspace-lookup-file "http://localhost:9229/foo/bar")
:to-be nil))
(it "can lookup file with .indium marker file"
(assess-with-filesystem indium-workspace--test-fs
(spy-on 'indium-repl-get-buffer :and-return-value (current-buffer))
(expect (indium-workspace-lookup-file "http://localhost:9229/js/app.js")
:to-equal (expand-file-name "js/app.js"))))
(it "should ignore query strings from urls when looking up files"
(assess-with-filesystem indium-workspace--test-fs
(spy-on 'indium-repl-get-buffer :and-return-value (current-buffer))
(expect (indium-workspace-lookup-file "http://localhost:9229/js/app.js?foo=bar")
:to-equal (expand-file-name "js/app.js"))))
(it "cannot find a file that does not exist"
(assess-with-filesystem indium-workspace--test-fs
(spy-on 'indium-repl-get-buffer :and-return-value (current-buffer))
(expect (indium-workspace-lookup-file "http://localhost:9229/non-existant-file-name.js")
:to-be nil))))
(describe "Looking up files safely"
(it "should fallback to the url when no file can be found"
(assess-with-filesystem indium-workspace--test-fs
(spy-on 'indium-repl-get-buffer :and-return-value (current-buffer))
(let ((url "http://localhost:9229/non-existant-file-name.js"))
(expect (indium-workspace-lookup-file-safe url)
:to-equal url))))
(it "can lookup files that exist"
(assess-with-filesystem indium-workspace--test-fs
(spy-on 'indium-repl-get-buffer :and-return-value (current-buffer))
(let ((url "http://localhost:9229/js/app.js")
(file (expand-file-name "js/app.js")))
(expect (indium-workspace-lookup-file-safe url)


Loading…
Cancel
Save