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.
 
 

266 lines
11 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:
(require 'dash)
(require 'ansi-color)
(require 'alchemist-utils)
(require 'alchemist-project)
(require 'alchemist-server)
(require 'alchemist-scope)
(require 'alchemist-goto)
(defgroup alchemist-help nil
"Functionality for Elixir documentation lookup."
:prefix "alchemist-help-"
:group 'alchemist)
(defcustom alchemist-help-buffer-name "*alchemist help*"
"Name of the Elixir help buffer."
:type 'string
:group 'alchemist-help)
(defvar alchemist-help-search-history '()
"Storage for the search history.")
(defvar alchemist-help-current-search-text '()
"Stores the current search.")
(defvar alchemist-help-filter-output nil)
(defface alchemist-help-key-face
'((t (:inherit font-lock-variable-name-face :bold t :foreground "red")))
"Face for the letter keys in the summary."
:group 'alchemist-help)
(defun alchemist-help-lookup-doc (search)
"Lookup Elixir documentation for SEARCH."
(setq alchemist-help-current-search-text search)
(setq alchemist-help-filter-output nil)
(if (not (alchemist-utils-empty-string-p search))
(alchemist-server-complete-candidates
(alchemist-help--completion-server-arguments search)
#'alchemist-help-complete-filter-output)
(message "No documentation for [%s] found." search)))
(defun alchemist-help-no-doc-available-p (string)
"Return non-nil if STRING contains Elixir no documentation message."
(or (string-match-p "No documentation for" string)
(string-match-p "Could not load module" string)
(string-match-p "it does not have Elixir-style docs" string)
(alchemist-utils-empty-string-p string)))
(defun alchemist-help-store-search-in-history ()
"Store the last `alchemist-help-current-search-text' in `alchemist-help-search-history'."
(unless (memq 'alchemist-help-current-search-text alchemist-help-search-history)
(add-to-list 'alchemist-help-search-history alchemist-help-current-search-text)))
(defun alchemist-help-display-doc (content)
"Initialize the `alchemist-help-buffer-name' and insert CONTENT."
(let ((default-directory (alchemist-project-root-or-default-dir))
(buffer (get-buffer-create alchemist-help-buffer-name)))
(cond
((alchemist-help-no-doc-available-p content)
(message (format "No documentation for [%s] found."
alchemist-help-current-search-text)))
(t
(alchemist-help-store-search-in-history)
(with-current-buffer buffer
(let ((inhibit-read-only t))
(goto-char (point-min))
(erase-buffer)
(insert content)
(goto-char (point-min))
(ansi-color-apply-on-region (point-min) (point-max))
(alchemist-help-minor-mode)))
(pop-to-buffer buffer)))))
(defun alchemist-help--search-at-point ()
"Search through `alchemist-help' with the expression under the cursor"
(let* ((expr (alchemist-scope-expression)))
(alchemist-help-lookup-doc (alchemist-help--prepare-search-expr expr))))
(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."
(let ((expr (buffer-substring-no-properties begin end)))
(alchemist-help-lookup-doc (alchemist-help--prepare-search-expr expr))))
(defun alchemist-help--prepare-search-expr (expr)
(let* ((module (alchemist-scope-extract-module expr))
(module (alchemist-scope-alias-full-path module))
(module (if module module ""))
(function (alchemist-scope-extract-function expr))
(function (if function function ""))
(expr (cond
((and (not (alchemist-utils-empty-string-p module))
(not (alchemist-utils-empty-string-p function)))
(format "%s.%s" module function))
((not (alchemist-utils-empty-string-p module))
module)
(t
expr))))
expr))
(defun alchemist-help--elixir-modules-to-list (str)
(let* ((str (replace-regexp-in-string "^Elixir\\." "" str))
(modules (split-string str))
(modules (delete nil modules))
(modules (cl-sort modules 'string-lessp :key 'downcase))
(modules (-distinct modules)))
modules))
(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 "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--server-arguments (args)
(if (and (not (equal major-mode 'alchemist-iex-mode))
(not (bound-and-true-p alchemist-help-minor-mode)))
(let* ((modules (alchemist-utils-prepare-modules-for-elixir
(alchemist-scope-all-modules))))
(format "{ \"%s\", [ context: Elixir, imports: %s, aliases: [] ] }" args modules))
(format "{ \"%s\", [ context: Elixir, imports: [], aliases: [] ] }" args)))
(defun alchemist-help--completion-server-arguments (args)
"Build informations about the current context."
(if (and (not (equal major-mode 'alchemist-iex-mode))
(not (bound-and-true-p alchemist-help-minor-mode)))
(let* ((modules (alchemist-utils-prepare-modules-for-elixir
(alchemist-scope-all-modules)))
(aliases (alchemist-utils-prepare-aliases-for-elixir
(alchemist-scope-aliases))))
(format "{ \"%s\", [ context: Elixir, imports: %s, aliases: %s ] }" args modules aliases))
(format "{ \"%s\", [ context: Elixir, imports: [], aliases: [] ] }" args)))
(defun alchemist-help-complete-filter-output (_process output)
(with-local-quit
(setq alchemist-help-filter-output (cons output alchemist-help-filter-output))
(if (alchemist-server-contains-end-marker-p output)
(let* ((string (alchemist-server-prepare-filter-output alchemist-help-filter-output))
(candidates (alchemist-complete--output-to-list
(ansi-color-filter-apply string)))
(candidates (if (= (length candidates) 2)
nil
candidates)))
(setq alchemist-help-filter-output nil)
(if candidates
(let* ((search (alchemist-complete--completing-prompt alchemist-help-current-search-text candidates)))
(setq alchemist-help-current-search-text search)
(alchemist-server-help (alchemist-help--server-arguments search) #'alchemist-help-filter-output))
(alchemist-server-help (alchemist-help--server-arguments alchemist-help-current-search-text) #'alchemist-help-filter-output))))))
(defun alchemist-help-filter-output (_process output)
(setq alchemist-help-filter-output (cons output alchemist-help-filter-output))
(if (alchemist-server-contains-end-marker-p output)
(let ((string (alchemist-server-prepare-filter-output alchemist-help-filter-output)))
(alchemist-help-display-doc string)
(setq alchemist-help-current-search-text nil)
(setq alchemist-help-filter-output nil))))
(defun alchemist-help-modules-filter (_process output)
(with-local-quit
(setq alchemist-help-filter-output (cons output alchemist-help-filter-output))
(if (alchemist-server-contains-end-marker-p output)
(let* ((output (alchemist-server-prepare-filter-output alchemist-help-filter-output))
(modules (alchemist-help--elixir-modules-to-list output))
(search (completing-read
"Elixir help: "
modules
nil
nil
nil))
(module (alchemist-scope-extract-module search))
(function (alchemist-scope-extract-function search))
(search (cond
((and module function)
search)
((and module
(not (string-match-p "[\/0-9]+$" module)))
(concat module "."))
(t
search))))
(alchemist-help-lookup-doc (alchemist-utils-remove-dot-at-the-end search))))))
;; Public functions
(defun alchemist-help-search-at-point ()
"Search through `alchemist-help' with the expression under the cursor.
If the buffer local variable `mark-active' is non-nil,
the actively marked region will be used for passing to `alchemist-help'."
(interactive)
(if mark-active
(alchemist-help--search-marked-region (region-beginning) (region-end))
(alchemist-help--search-at-point)))
(defvar alchemist-help-minor-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "q") #'quit-window)
(define-key map (kbd "e") #'alchemist-help-search-at-point)
(define-key map (kbd "s") #'alchemist-help)
(define-key map (kbd "h") #'alchemist-help-history)
(define-key map (kbd "M-.") #'alchemist-goto-definition-at-point)
(define-key map (kbd "?") #'alchemist-help-minor-mode-key-binding-summary)
map)
"Keymap for `alchemist-help-minor-mode'.")
(define-minor-mode alchemist-help-minor-mode
"Minor mode for displaying elixir help."
:group 'alchemist-help
:keymap alchemist-help-minor-mode-map
(cond (alchemist-help-minor-mode
(setq buffer-read-only t))
(t
(setq buffer-read-only nil))))
(defun alchemist-help ()
"Load Elixir documentation for SEARCH."
(interactive)
(setq alchemist-help-filter-output nil)
(alchemist-server-info "{ :type, :modules }" #'alchemist-help-modules-filter))
(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-lookup-doc search))
(provide 'alchemist-help)
;;; alchemist-help.el ends here