2013-08-27 15:58:26 -06:00
|
|
|
;;;
|
|
|
|
;;; Integration of the Go 'oracle' analysis tool into Emacs.
|
|
|
|
;;;
|
|
|
|
;;; To install the Go oracle, run:
|
|
|
|
;;; % export GOROOT=... GOPATH=...
|
|
|
|
;;; % go get code.google.com/p/go.tools/cmd/oracle
|
|
|
|
;;; % mv $GOPATH/bin/oracle $GOROOT/bin/
|
|
|
|
;;;
|
|
|
|
;;; Load this file into Emacs and set go-oracle-scope to your
|
2013-09-12 20:34:38 -06:00
|
|
|
;;; configuration. Then, find a file of Go source code, enable
|
|
|
|
;;; go-oracle-mode, select an expression of interest, and press `C-c C-o d'
|
|
|
|
;;; (for "describe") or run one of the other go-oracle-xxx commands.
|
2013-08-27 15:58:26 -06:00
|
|
|
;;;
|
|
|
|
;;; TODO(adonovan): simplify installation and configuration by making
|
|
|
|
;;; oracle a subcommand of 'go tool'.
|
|
|
|
|
|
|
|
(require 'compile)
|
|
|
|
(require 'go-mode)
|
|
|
|
(require 'cl)
|
|
|
|
|
|
|
|
(defgroup go-oracle nil
|
|
|
|
"Options specific to the Go oracle."
|
|
|
|
:group 'go)
|
|
|
|
|
|
|
|
(defcustom go-oracle-command (concat (car (go-root-and-paths)) "/bin/oracle")
|
|
|
|
"The Go oracle command; the default is $GOROOT/bin/oracle."
|
|
|
|
:type 'string
|
|
|
|
:group 'go-oracle)
|
|
|
|
|
|
|
|
(defcustom go-oracle-scope ""
|
|
|
|
"The scope of the analysis. See `go-oracle-set-scope'."
|
|
|
|
:type 'string
|
|
|
|
:group 'go-oracle)
|
|
|
|
|
|
|
|
(defvar go-oracle--scope-history
|
|
|
|
nil
|
|
|
|
"History of values supplied to `go-oracle-set-scope'.")
|
|
|
|
|
2013-12-13 08:04:55 -07:00
|
|
|
;; TODO(adonovan): I'd like to get rid of this separate mode since it
|
|
|
|
;; makes it harder to use the oracle.
|
2013-09-12 20:34:38 -06:00
|
|
|
(defvar go-oracle-mode-map
|
|
|
|
(let ((m (make-sparse-keymap)))
|
2013-12-13 08:04:55 -07:00
|
|
|
(define-key m (kbd "C-c C-o t") #'go-oracle-describe) ; t for type
|
2013-09-12 20:34:38 -06:00
|
|
|
(define-key m (kbd "C-c C-o f") #'go-oracle-freevars)
|
|
|
|
(define-key m (kbd "C-c C-o g") #'go-oracle-callgraph)
|
|
|
|
(define-key m (kbd "C-c C-o i") #'go-oracle-implements)
|
2013-12-13 08:04:55 -07:00
|
|
|
(define-key m (kbd "C-c C-o c") #'go-oracle-peers) ; c for channel
|
2013-09-12 20:34:38 -06:00
|
|
|
(define-key m (kbd "C-c C-o r") #'go-oracle-referrers)
|
2013-12-13 08:04:55 -07:00
|
|
|
(define-key m (kbd "C-c C-o d") #'go-oracle-definition)
|
|
|
|
(define-key m (kbd "C-c C-o p") #'go-oracle-pointsto)
|
2013-09-12 20:34:38 -06:00
|
|
|
(define-key m (kbd "C-c C-o s") #'go-oracle-callstack)
|
|
|
|
(define-key m (kbd "C-c C-o <") #'go-oracle-callers)
|
|
|
|
(define-key m (kbd "C-c C-o >") #'go-oracle-callees)
|
|
|
|
m))
|
|
|
|
|
|
|
|
;; TODO(dominikh): Rethink set-scope some. Setting it to a file is
|
|
|
|
;; painful because it doesn't use find-file, and variables/~ aren't
|
|
|
|
;; expanded. Setting it to an import path is somewhat painful because
|
|
|
|
;; it doesn't make use of go-mode's import path completion. One option
|
|
|
|
;; would be having two different functions, but then we can't
|
|
|
|
;; automatically call it when no scope has been set. Also it wouldn't
|
|
|
|
;; easily allow specifying more than one file/package.
|
2013-08-27 15:58:26 -06:00
|
|
|
(defun go-oracle-set-scope ()
|
2013-09-12 20:34:38 -06:00
|
|
|
"Set the scope for the Go oracle, prompting the user to edit the
|
2013-08-27 15:58:26 -06:00
|
|
|
previous scope.
|
|
|
|
|
|
|
|
The scope specifies a set of arguments, separated by spaces.
|
|
|
|
It may be:
|
|
|
|
1) a set of packages whose main() functions will be analyzed.
|
|
|
|
2) a list of *.go filenames; they will treated like as a single
|
|
|
|
package (see #3).
|
|
|
|
3) a single package whose main() function and/or Test* functions
|
|
|
|
will be analyzed.
|
|
|
|
|
|
|
|
In the common case, this is similar to the argument(s) you would
|
|
|
|
specify to 'go build'."
|
|
|
|
(interactive)
|
|
|
|
(let ((scope (read-from-minibuffer "Go oracle scope: "
|
|
|
|
go-oracle-scope
|
|
|
|
nil
|
|
|
|
nil
|
|
|
|
'go-oracle--scope-history)))
|
|
|
|
(if (string-equal "" scope)
|
|
|
|
(error "You must specify a non-empty scope for the Go oracle"))
|
|
|
|
(setq go-oracle-scope scope)))
|
|
|
|
|
|
|
|
(defun go-oracle--run (mode)
|
|
|
|
"Run the Go oracle in the specified MODE, passing it the
|
|
|
|
selected region of the current buffer. Process the output to
|
|
|
|
replace each file name with a small hyperlink. Display the
|
|
|
|
result."
|
|
|
|
(if (not buffer-file-name)
|
|
|
|
(error "Cannot use oracle on a buffer without a file name"))
|
|
|
|
;; It's not sufficient to save a modified buffer since if
|
|
|
|
;; gofmt-before-save is on the before-save-hook, saving will
|
|
|
|
;; disturb the selected region.
|
|
|
|
(if (buffer-modified-p)
|
|
|
|
(error "Please save the buffer before invoking go-oracle"))
|
|
|
|
(if (string-equal "" go-oracle-scope)
|
|
|
|
(go-oracle-set-scope))
|
|
|
|
(let* ((filename (file-truename buffer-file-name))
|
2013-09-03 08:58:58 -06:00
|
|
|
(posflag (if (use-region-p)
|
2013-09-08 20:10:11 -06:00
|
|
|
(format "-pos=%s:#%d,#%d"
|
2013-09-03 08:58:58 -06:00
|
|
|
filename
|
|
|
|
(1- (go--position-bytes (region-beginning)))
|
|
|
|
(1- (go--position-bytes (region-end))))
|
2013-09-04 12:35:24 -06:00
|
|
|
(format "-pos=%s:#%d"
|
2013-09-03 08:58:58 -06:00
|
|
|
filename
|
|
|
|
(1- (position-bytes (point))))))
|
2013-08-27 15:58:26 -06:00
|
|
|
;; This would be simpler if we could just run 'go tool oracle'.
|
|
|
|
(env-vars (go-root-and-paths))
|
|
|
|
(goroot-env (concat "GOROOT=" (car env-vars)))
|
|
|
|
(gopath-env (concat "GOPATH=" (mapconcat #'identity (cdr env-vars) ":"))))
|
|
|
|
(with-current-buffer (get-buffer-create "*go-oracle*")
|
|
|
|
(setq buffer-read-only nil)
|
|
|
|
(erase-buffer)
|
|
|
|
(insert "Go Oracle\n")
|
2013-09-25 12:34:39 -06:00
|
|
|
(let ((args (append (list go-oracle-command nil t nil posflag mode)
|
2013-08-27 15:58:26 -06:00
|
|
|
(split-string go-oracle-scope " " t))))
|
|
|
|
;; Log the command to *Messages*, for debugging.
|
|
|
|
(message "Command: %s:" args)
|
|
|
|
(message nil) ; clears/shrinks minibuffer
|
|
|
|
|
|
|
|
(message "Running oracle...")
|
|
|
|
;; Use dynamic binding to modify/restore the environment
|
2013-09-04 13:20:38 -06:00
|
|
|
(let ((process-environment (list* goroot-env gopath-env process-environment)))
|
2013-08-27 15:58:26 -06:00
|
|
|
(apply #'call-process args)))
|
|
|
|
(insert "\n")
|
|
|
|
(compilation-mode)
|
|
|
|
(setq compilation-error-screen-columns nil)
|
|
|
|
|
|
|
|
;; Hide the file/line info to save space.
|
|
|
|
;; Replace each with a little widget.
|
|
|
|
;; compilation-mode + this loop = slooow.
|
2013-12-13 08:04:55 -07:00
|
|
|
;; TODO(adonovan): have oracle give us JSON
|
2013-08-27 15:58:26 -06:00
|
|
|
;; and we'll do the markup directly.
|
|
|
|
(let ((buffer-read-only nil)
|
|
|
|
(p 1))
|
|
|
|
(while (not (null p))
|
|
|
|
(let ((np (compilation-next-single-property-change p 'compilation-message)))
|
2013-08-29 19:32:49 -06:00
|
|
|
;; TODO(adonovan): this can be verbose in the *Messages* buffer.
|
2013-09-03 13:41:05 -06:00
|
|
|
;; (message "Post-processing link (%d%%)" (/ (* p 100) (point-max)))
|
2013-08-27 15:58:26 -06:00
|
|
|
(if np
|
|
|
|
(when (equal (line-number-at-pos p) (line-number-at-pos np))
|
|
|
|
;; np is (typically) the space following ":"; consume it too.
|
|
|
|
(put-text-property p np 'display "▶")
|
|
|
|
(goto-char np)
|
|
|
|
(insert " ")))
|
|
|
|
(setq p np)))
|
2013-09-03 13:41:05 -06:00
|
|
|
(message nil))
|
|
|
|
|
|
|
|
(let ((w (display-buffer (current-buffer))))
|
|
|
|
(balance-windows)
|
|
|
|
(shrink-window-if-larger-than-buffer w)
|
|
|
|
(set-window-point w (point-min))))))
|
2013-08-27 15:58:26 -06:00
|
|
|
|
|
|
|
(defun go-oracle-callees ()
|
|
|
|
"Show possible callees of the function call at the current point."
|
|
|
|
(interactive)
|
|
|
|
(go-oracle--run "callees"))
|
|
|
|
|
|
|
|
(defun go-oracle-callers ()
|
|
|
|
"Show the set of callers of the function containing the current point."
|
|
|
|
(interactive)
|
|
|
|
(go-oracle--run "callers"))
|
|
|
|
|
|
|
|
(defun go-oracle-callgraph ()
|
|
|
|
"Show the callgraph of the current program."
|
|
|
|
(interactive)
|
|
|
|
(go-oracle--run "callgraph"))
|
|
|
|
|
|
|
|
(defun go-oracle-callstack ()
|
|
|
|
"Show an arbitrary path from a root of the call graph to the
|
|
|
|
function containing the current point."
|
|
|
|
(interactive)
|
|
|
|
(go-oracle--run "callstack"))
|
|
|
|
|
2013-12-13 08:04:55 -07:00
|
|
|
(defun go-oracle-definition ()
|
|
|
|
"Show the definition of the selected identifier."
|
|
|
|
(interactive)
|
|
|
|
(go-oracle--run "definition"))
|
|
|
|
|
2013-08-27 15:58:26 -06:00
|
|
|
(defun go-oracle-describe ()
|
2013-12-13 08:04:55 -07:00
|
|
|
"Describe the selected syntax, its kind, type and methods."
|
2013-08-27 15:58:26 -06:00
|
|
|
(interactive)
|
|
|
|
(go-oracle--run "describe"))
|
|
|
|
|
2013-12-13 08:04:55 -07:00
|
|
|
(defun go-oracle-pointsto ()
|
|
|
|
"Show what the selected expression points to."
|
|
|
|
(interactive)
|
|
|
|
(go-oracle--run "pointsto"))
|
|
|
|
|
2013-08-27 15:58:26 -06:00
|
|
|
(defun go-oracle-implements ()
|
|
|
|
"Describe the 'implements' relation for types in the package
|
|
|
|
containing the current point."
|
|
|
|
(interactive)
|
|
|
|
(go-oracle--run "implements"))
|
|
|
|
|
|
|
|
(defun go-oracle-freevars ()
|
|
|
|
"Enumerate the free variables of the current selection."
|
|
|
|
(interactive)
|
|
|
|
(go-oracle--run "freevars"))
|
|
|
|
|
2013-09-03 13:41:05 -06:00
|
|
|
(defun go-oracle-peers ()
|
2013-08-27 15:58:26 -06:00
|
|
|
"Enumerate the set of possible corresponding sends/receives for
|
|
|
|
this channel receive/send operation."
|
|
|
|
(interactive)
|
|
|
|
(go-oracle--run "peers"))
|
|
|
|
|
2013-09-10 12:11:42 -06:00
|
|
|
(defun go-oracle-referrers ()
|
|
|
|
"Enumerate all references to the object denoted by the selected
|
|
|
|
identifier."
|
|
|
|
(interactive)
|
|
|
|
(go-oracle--run "referrers"))
|
|
|
|
|
2013-09-12 20:34:38 -06:00
|
|
|
;; TODO(dominikh): better docstring
|
|
|
|
(define-minor-mode go-oracle-mode "Oracle minor mode for go-mode
|
|
|
|
|
|
|
|
Keys specific to go-oracle-mode:
|
|
|
|
\\{go-oracle-mode-map}"
|
|
|
|
nil " oracle" go-oracle-mode-map)
|
2013-08-27 15:58:26 -06:00
|
|
|
|
|
|
|
(provide 'go-oracle)
|