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.
 
 

264 lines
10 KiB

;;; alchemist-help.el --- Functionality for Elixir documentation lookup -*- lexical-binding: t -*-
;; Copyright © 2014-2015 Samuel Tonini
;; Author: Samuel Tonini <tonini.samuel@gmail.com
;; This file is not part of GNU Emacs.
;; 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:
;; Functionality for Elixir documentation lookup.
;;; Code:
(defgroup alchemist-help nil
"Functionality for Elixir documentation lookup."
:prefix "alchemist-help-"
:group 'alchemist)
;; Variables
(defcustom alchemist-help-ansi-color-docs t
"If t, `alchemist-help' will present ansi colored documentation."
:type 'boolean
:group 'alchemist-help)
(defcustom alchemist-help-buffer-name "*elixir help*"
"Name of the Elixir help buffer."
:type 'string
:group 'alchemist-help)
(defvar alchemist-help-mix-run-command "mix run"
"The shell command for 'mix run'.")
(defvar alchemist-help-search-history '()
"Storage for the search history.")
(defvar alchemist-help-current-search-text '()
"Stores the current search.")
;; Faces
(defface alchemist-help--key-face
'((t (:inherit font-lock-variable-name-face :bold t :foreground "red")))
"Fontface for the letter keys in the summary."
:group 'alchemist-help)
(defun alchemist-help--load-ansi-color-setting ()
(let ((config (gethash "ansi-color-docs" (alchemist-project-config))))
(if config
(intern config)
alchemist-help-ansi-color-docs)))
(defun alchemist-help--exp-at-point ()
"Return the expression under the cursor."
(let (p1 p2)
(save-excursion
(skip-chars-backward "-_A-Za-z0-9.?!:")
(setq p1 (point))
(skip-chars-forward "-_A-Za-z0-9.?!:")
(setq p2 (point))
(buffer-substring-no-properties p1 p2))))
(defun alchemist-help--start-help-process (exp callback)
(let* ((buffer (get-buffer-create "alchemist-help-buffer"))
(command (alchemist-help--eval-string-command (alchemist-help--build-code-for-search exp)))
(proc (start-process-shell-command "alchemist-help-proc" buffer command)))
(set-process-sentinel proc (lambda (process signal)
(when (equal signal "finished\n")
(funcall callback (alchemist-utils--get-buffer-content (process-buffer process))))
(alchemist-utils--erase-buffer (process-buffer process))))))
(defun alchemist-help--execute (search)
(let ((last-directory default-directory)
(last-buffer (current-buffer)))
(alchemist-complete search (lambda (candidates)
(if candidates
(let* ((search (alchemist-complete--completing-prompt search candidates)))
(setq alchemist-help-current-search-text search)
(alchemist-help--start-help-process search (lambda (output)
(alchemist-help--initialize-buffer output)
(with-current-buffer last-buffer
(cd last-directory)))))
(message "No documentation found for '%s'" search))))))
(defun alchemist-help--execute-without-complete (search)
(setq alchemist-help-current-search-text search)
(let ((last-directory default-directory)
(last-buffer (current-buffer)))
(alchemist-help--start-help-process search (lambda (output)
(alchemist-help--initialize-buffer output)
(with-current-buffer last-buffer
(cd last-directory))))))
(defun alchemist-help--build-code-for-search (string)
(format "import IEx.Helpers
Application.put_env(:iex, :colors, [enabled: %s])
h(%s)" (if (alchemist-help--load-ansi-color-setting) "true" "false") string))
(defun alchemist-help--eval-string-command (string)
(when (alchemist-project-p)
(alchemist-project--establish-root-directory))
(let* ((compile-option (if (and (alchemist-project-p)
(alchemist-project--load-compile-when-needed-setting))
""
"--no-compile"))
(command (if (alchemist-project-p)
(format "%s %s -e \"%s\"" alchemist-help-mix-run-command compile-option string)
(format "%s -e \"%s\"" alchemist-execute-command string))))
command))
(defun alchemist-help--bad-search-output-p (string)
(let ((match (or (string-match-p "No documentation for " string)
(string-match-p "Invalid arguments for h helper" string)
(string-match-p "** (TokenMissingError)" string)
(string-match-p "** (SyntaxError)" string)
(string-match-p "** (FunctionClauseError)" string)
(string-match-p "** (CompileError)" string)
(string-match-p "Could not load module" string))))
(if match
t
nil)))
(defun alchemist-help--initialize-buffer (content)
(pop-to-buffer alchemist-help-buffer-name)
(setq buffer-undo-list nil)
(let ((inhibit-read-only t)
(buffer-undo-list t))
(cond ((alchemist-help--bad-search-output-p content)
(message (propertize
(format "No documentation for [ %s ] found." alchemist-help-current-search-text)
'face 'alchemist-help--key-face)))
(t
(erase-buffer)
(insert content)
(unless (memq 'alchemist-help-current-search-text alchemist-help-search-history)
(add-to-list 'alchemist-help-search-history alchemist-help-current-search-text))))
(delete-matching-lines "do not show this result in output" (point-min) (point-max))
(delete-matching-lines "^Compiled lib\\/" (point-min) (point-max))
(ansi-color-apply-on-region (point-min) (point-max))
(toggle-read-only 1)
(alchemist-help-minor-mode 1)))
(defun alchemist-help-minor-mode-key-binding-summary ()
(interactive)
(message
(concat "[" (propertize "q" 'face 'alchemist-help--key-face)
"]-quit ["
(propertize "e" 'face 'alchemist-help--key-face)
"]-search-at-point ["
(propertize "m" 'face 'alchemist-help--key-face)
"]-search-marked-region ["
(propertize "s" 'face 'alchemist-help--key-face)
"]-search ["
(propertize "h" 'face 'alchemist-help--key-face)
"]-history ["
(propertize "?" 'face 'alchemist-help--key-face)
"]-keys")))
(defun alchemist-help-search-at-point ()
"Search through `alchemist-help' with the expression under the cursor."
(interactive)
(alchemist-help--execute (alchemist-help--exp-at-point)))
(defun alchemist-help-search-marked-region (begin end)
"Run `alchemist-help' with the marked region.
Argument BEGIN where the mark starts.
Argument END where the mark ends."
(interactive "r")
(let ((region (filter-buffer-substring begin end)))
(alchemist-help--execute region)))
(defun alchemist-help--elixir-modules-to-list (str)
(let* ((modules (split-string str))
(modules (mapcar (lambda (m)
(when (string-match-p "Elixir\\." m)
(replace-regexp-in-string "Elixir\\." "" m))) modules))
(modules (delete nil modules))
(modules (cl-sort modules 'string-lessp :key 'downcase))
(modules (delete-dups modules)))
modules)
)
(defun alchemist-help--get-modules ()
(let* ((elixir-code "
defmodule AlchemistModule do
def get_modules do
modules = Enum.map(:code.all_loaded, fn({m, _}) -> Atom.to_string(m) end)
if :code.get_mode() === :interactive do
modules ++ get_modules_from_applications()
else
modules
end
end
defp get_modules_from_applications do
for {app, _, _} <- :application.loaded_applications,
{_, modules} = :application.get_key(app, :modules),
module <- modules,
has_doc = Code.get_docs(module, :moduledoc), elem(has_doc, 1) do
Atom.to_string(module)
end
end
end
AlchemistModule.get_modules |> Enum.map &IO.puts/1
")
(command (if (alchemist-project-p)
(format "%s -e \"%s\"" alchemist-help-mix-run-command elixir-code)
(format "%s -e \"%s\"" alchemist-execute-command elixir-code))))
(when (alchemist-project-p)
(alchemist-project--establish-root-directory))
(alchemist-help--elixir-modules-to-list (shell-command-to-string command))))
(define-minor-mode alchemist-help-minor-mode
"Minor mode for displaying elixir help."
:group 'alchemist-help
:keymap '(("q" . quit-window)
("e" . alchemist-help-search-at-point)
("m" . alchemist-help-search-marked-region)
("s" . alchemist-help)
("h" . alchemist-help-history)
("?" . alchemist-help-minor-mode-key-binding-summary)))
(defun alchemist-help (search)
"Load Elixir documentation for SEARCH."
(interactive
(list (completing-read
"Elixir help: "
(alchemist-help--get-modules)
nil
nil
nil)))
(alchemist-help--execute (if (string-match-p "\\.$" search)
search
(concat search "."))))
(defun alchemist-help-history (search)
"Load Elixir from the documentation history for SEARCH."
(interactive
(list
(completing-read "Elixir help history: " alchemist-help-search-history nil nil "")))
(alchemist-help--execute-without-complete search))
(provide 'alchemist-help)
;;; alchemist-help.el ends here