Init-Files/site-lisp/fb-glass.el
2021-08-16 16:30:19 -07:00

105 lines
4.3 KiB
EmacsLisp

;;; fb-glass.el --- Emacs integration for the Glass CLI -*- lexical-binding: t; -*-
;; Keywords: tools,processes
;; Package-Requires: ((emacs "27.1") (f "1.0"))
;; Version: 0.1
;;; Commentary:
;; Integrates `glass` into Emacs. Improvements welcome!
;; Note that Glass is designed more for static/"offline" navigation of code, without needing
;; a full LSP to be running.
;; This integration will return stale results if you try to use it on files that have been modified.
;;; Code:
(require 'compile)
(require 'f)
(defun glass--call-process-shell-check-success (command)
(with-temp-buffer
(let ((exit-code (call-process-shell-command command nil (current-buffer))))
(unless (equal exit-code 0)
(error "Error running shell command"))
(buffer-string))))
(defun glass--root (filename)
"Gets the root corresponding to the given filename. If none given, the current buffer's filename is used."
(let ((default-directory (file-name-directory (file-truename filename))))
(string-trim (glass--call-process-shell-check-success "hg root"))))
(defun glass--repo (filename)
"Gets the Glean repo ID corresponding to the given filename. If none given, the current buffer's filename is used."
(let ((default-directory (file-name-directory (file-truename filename))))
(string-trim (glass--call-process-shell-check-success "hg config remotefilelog.reponame"))))
;;; VISIT SYMBOLS
(defun glass-visit-symbol (symbol)
"Given a Glass symbol, visit the file that defines that symbol"
(interactive)
(let* ((output (glass--call-process-shell-check-success
(format "glass --caller=emacs describe -t %s" (shell-quote-argument symbol))))
(position (split-string (car (split-string output)) ":"))
(filename (car position))
(local-filename (concat "~/" filename)))
(if (file-exists-p local-filename)
(progn
(find-file local-filename)
(goto-char (point-min))
(forward-line (- (string-to-number (cadr position)) 1))
(forward-char (- (string-to-number (caddr position)) 1)))
(let* ((buffer (generate-new-buffer (concat "glass--" (file-name-nondirectory filename))))
(repo (car (split-string filename "/")))
(exit-code (call-process "scsc" nil buffer nil
"cat"
"--repo" (shell-quote-argument repo)
"--path" (shell-quote-argument (string-remove-prefix repo filename))
"-B" "master")))
(if (equal exit-code 0)
(progn
(with-current-buffer buffer
(setq-local buffer-read-only t)
(goto-char (point-min))
(forward-line (- (string-to-number (cadr position)) 1))
(forward-char (- (string-to-number (caddr position)) 1)))
(pop-to-buffer buffer))
(progn
(kill-buffer buffer)
(error "Couldn't display source with `scsc`")))))))
;;; LIST SYMBOLS
(defun glass-list-symbols (&optional filename)
"Lists all symbols within the given filesystem path. If none given, the current buffer's filename is used."
(let* ((file (file-truename (or filename (buffer-file-name))))
(root (glass--root file))
(repo (glass--repo file))
(search-path (concat repo "/" (file-relative-name file root))))
;; TODO integrate with myles, myles to get a path -> glass to list its symbols
(compilation-start
(format "glass --caller emacs list-symbols %s | sed -e 's|^|~/|'" search-path))))
;;; SYMBOL-SEARCH
(with-eval-after-load "counsel"
(defun glass--symbol-search (input)
(counsel--async-command
(format "glass --caller=emacs symbol-search %s"
(shell-quote-argument input)))
nil)
(defun glass-symbol-search-counsel ()
"Find a symbol by full symbol ID prefix using `glass symbol-search`"
(interactive)
(counsel-require-program "glass")
(let ((default-directory (myles--root)))
;; TODO allow entering "/" to grab the current candidate and restart from there
;; similar to how it works for counsel-find-file
(ivy-read
"symbol prefix: "
#'glass--symbol-search
:caller 'glass-counsel
:dynamic-collection t
:action #'glass-visit-symbol))))
(provide 'fb-glass)
;;; fb-glass.el ends here