;;; alchemist-help.el --- Functionality for Elixir documentation lookup -*- lexical-binding: t -*- ;; Copyright © 2014-2015 Samuel Tonini ;; Author: Samuel Tonini . ;;; 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