| @ -1,231 +0,0 @@ | |||
| ;;; alchemist-goto.el --- Functionality to jump modules and function definitions | |||
| ;; Copyright © 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 to jump modules and function definitions | |||
| ;;; Code: | |||
| (defgroup alchemist-goto nil | |||
| "Functionality to jump modules and function definitions." | |||
| :prefix "alchemist-goto-" | |||
| :group 'alchemist) | |||
| (defcustom alchemist-goto-erlang-source-dir "" | |||
| "Path to the erlang source code." | |||
| :type 'string | |||
| :group 'alchemist-goto) | |||
| (defcustom alchemist-goto-elixir-source-dir "" | |||
| "Path to the elixir source code." | |||
| :type 'string | |||
| :group 'alchemist-goto) | |||
| (defun alchemist-goto--extract-module (code) | |||
| "Extract module from CODE." | |||
| (let* ((parts (split-string code "\\.")) | |||
| (function (car (last parts))) | |||
| (case-fold-search nil)) | |||
| (when (string-match-p "^[a-z]+" function) | |||
| (delete function parts)) | |||
| (unless (string-match-p "^[a-z]+" (car parts)) | |||
| (mapconcat 'concat parts ".")))) | |||
| (defun alchemist-goto--extract-function (code) | |||
| "Extract function from CODE." | |||
| (let* ((parts (split-string code "\\.")) | |||
| (function (car (last parts))) | |||
| (case-fold-search nil)) | |||
| (when (and function | |||
| (string-match-p "^[a-z]+" function)) | |||
| function))) | |||
| (defun alchemist-goto--build-elixir-ex-core-file (file) | |||
| (when (string-match "\\/\\(lib\\/.+\\/lib\\)\\/.+\.ex$" file) | |||
| (let* ((file (substring-no-properties file (match-beginning 1))) | |||
| (source-directory (expand-file-name alchemist-goto-elixir-source-dir))) | |||
| (concat source-directory file)))) | |||
| (defun alchemist-goto--build-elixir-erl-core-file (file) | |||
| (when (string-match "\\/\\(lib\\/.+\\/src\\)\\/.+\.erl$" file) | |||
| (let* ((file (substring-no-properties file (match-beginning 1))) | |||
| (source-directory (expand-file-name alchemist-goto-elixir-source-dir))) | |||
| (concat source-directory file)))) | |||
| (defun alchemist-goto--build-erlang-core-file (file) | |||
| (when (string-match "\\/\\(lib\\/.+\\/src\\)\\/.+\.erl$" file) | |||
| (let* ((file (substring-no-properties file (match-beginning 1))) | |||
| (source-directory (expand-file-name alchemist-goto-erlang-source-dir))) | |||
| (concat source-directory file)))) | |||
| (defun alchemist-goto--elixir-file-p (file) | |||
| (string-match-p "\\.ex\\(s\\)?$" file)) | |||
| (defun alchemist-goto--erlang-file-p (file) | |||
| (string-match-p "\\.erl$" file)) | |||
| (defun alchemist-goto--get-full-path-of-alias (module) | |||
| (let* ((aliases (mapcar (lambda (m) | |||
| (when (string= module (car (cdr m))) | |||
| (car m))) (alchemist-goto--alises-of-current-buffer))) | |||
| (aliases (delete nil aliases))) | |||
| (if aliases | |||
| (car aliases) | |||
| module))) | |||
| (defun alchemist-goto--open-definition (expr) | |||
| (let* ((module (alchemist-goto--extract-module expr)) | |||
| (module (alchemist-goto--get-full-path-of-alias module)) | |||
| (module (if module module "AlchemistGoto")) | |||
| (function (alchemist-goto--extract-function expr)) | |||
| (function (if function function "\"\"")) | |||
| (file (alchemist-goto--get-module-source module function))) | |||
| (ring-insert find-tag-marker-ring (point-marker)) | |||
| (cond ((equal file nil) | |||
| (message "Don't know how to find: %s" expr)) | |||
| ((file-exists-p file) | |||
| (alchemist-goto--open-file file module function)) | |||
| ((alchemist-goto--elixir-file-p file) | |||
| (let* ((elixir-source-file (alchemist-goto--build-elixir-ex-core-file file))) | |||
| (if (file-exists-p elixir-source-file) | |||
| (alchemist-goto--open-file elixir-source-file module function) | |||
| (message "Don't know how to find: %s" expr)))) | |||
| ((alchemist-goto--erlang-file-p file) | |||
| (let* ((elixir-source-file (alchemist-goto--build-elixir-erl-core-file file)) | |||
| (erlang-source-file (alchemist-goto--build-erlang-core-file file))) | |||
| (cond ((file-exists-p elixir-source-file) | |||
| (alchemist-goto--open-file elixir-source-file module function)) | |||
| ((file-exists-p erlang-source-file) | |||
| (alchemist-goto--open-file erlang-source-file module function)) | |||
| (t | |||
| (message "Don't know how to find: %s" expr))))) | |||
| (t | |||
| (pop-tag-mark) | |||
| (message "Don't know how to find: %s" expr))))) | |||
| (defun alchemist-goto--open-file (file module function) | |||
| (let* ((buf (find-file-noselect file))) | |||
| (switch-to-buffer buf) | |||
| (beginning-of-buffer) | |||
| (cond ((alchemist-goto--elixir-file-p file) | |||
| (alchemist-goto--jump-to-elixir-source module function)) | |||
| ((alchemist-goto--erlang-file-p file) | |||
| (alchemist-goto--jump-to-erlang-source module function))))) | |||
| (defun alchemist-goto--jump-to-elixir-source (module function) | |||
| (let ((function (replace-regexp-in-string "\?" "\\?" function))) | |||
| (when (re-search-forward (format "^\s+\\(defp?\s+%s\(\\|defmacrop?\s+%s\(\\)" function function function) nil t) | |||
| (goto-char (match-beginning 0))) | |||
| (when (re-search-forward (format "\\(defmodule\\|defimpl\\|defprotocol\\)\s+%s\s+do" module) nil t) | |||
| (goto-char (match-beginning 0))))) | |||
| (defun alchemist-goto--jump-to-erlang-source (module function) | |||
| (when (re-search-forward (format "\\(^%s\(\\)" function) nil t) | |||
| (goto-char (match-beginning 0))) | |||
| (when (re-search-forward (format "\\(^-module\(%s\)\\)" (substring module 1)) nil t) | |||
| (goto-char (match-beginning 0)))) | |||
| (defun alchemist-goto--clear-output (output) | |||
| (let* ((output (replace-regexp-in-string "source-file-path:" "" output)) | |||
| (output (replace-regexp-in-string "\n" "" output)) | |||
| (output (alchemist--utils-clear-ansi-sequences output)) | |||
| (output (if (string= output "") nil output))) | |||
| output)) | |||
| (defun alchemist-goto--debug-message (output) | |||
| (alchemist-message (format "== ALCHEMIST GOTO FAILED ==\n== OUTPUT BEGIN:\n%s== OUTPUT END:" | |||
| output))) | |||
| (defun alchemist-goto--report-errors (output) | |||
| (when (and (not (string-match-p "source-file-path:" output)) | |||
| (not (string= (alchemist--utils-clear-ansi-sequences | |||
| (replace-regexp-in-string "\n" "" output)) ""))) | |||
| (when alchemist-complete-debug-mode | |||
| (alchemist-goto--debug-message output)))) | |||
| (defun alchemist-goto--runner () | |||
| (if (alchemist-project-p) | |||
| (format "%s run --no-compile" alchemist-mix-command) | |||
| alchemist-execute-command)) | |||
| (defun alchemist-goto--get-module-source (module function) | |||
| (let* ((default-directory (if (alchemist-project-p) | |||
| (alchemist-project-root) | |||
| default-directory)) | |||
| (source-file (shell-command-to-string (format "%s -e '%s'" | |||
| (alchemist-goto--runner) | |||
| (alchemist-goto--get-module-source-code module function))))) | |||
| (alchemist-goto--report-errors source-file) | |||
| (alchemist-goto--clear-output source-file))) | |||
| (defun alchemist-goto--get-module-source-code (module function) | |||
| (format " | |||
| defmodule Source do | |||
| def find(module, function) do | |||
| cond do | |||
| Code.ensure_loaded?(module) -> | |||
| IO.puts source(module) | |||
| List.keymember?(Kernel.module_info[:exports], function, 0) -> | |||
| IO.puts source(Kernel) | |||
| true -> | |||
| IO.puts \"\" | |||
| end | |||
| end | |||
| defp source(module) do | |||
| source = module.module_info(:compile)[:source] | |||
| case source do | |||
| nil -> nil | |||
| source -> \"source-file-path:\" <> List.to_string(source) | |||
| end | |||
| end | |||
| end | |||
| Source.find(%s, :%s)" module function)) | |||
| (defun alchemist-goto--alises-of-current-buffer () | |||
| (let* ((aliases '())) | |||
| (save-excursion | |||
| (goto-char (point-min)) | |||
| (while (re-search-forward "^\s+alias\s+\\([-_A-Za-z0-9,\.\?!\]+\\)\\(\s*,\s*as:\s*\\)?\\([-_A-Za-z0-9,\.\?!\]+\\)?\n" nil t) | |||
| (let* ((alias (match-string 1)) | |||
| (as (if (match-string 3) (match-string 3) nil)) | |||
| (as (if as as (car (last (split-string alias "\\.")))))) | |||
| (setq aliases (append aliases (list (list alias as))))))) | |||
| aliases)) | |||
| (defun alchemist-goto-definition-at-point () | |||
| "Jump to the elixir expression definition at point." | |||
| (interactive) | |||
| (let (p1 p2) | |||
| (skip-chars-backward "-_A-Za-z0-9.?!:") | |||
| (setq p1 (point)) | |||
| (skip-chars-forward "-_A-Za-z0-9.?!:") | |||
| (setq p2 (point)) | |||
| (alchemist-goto--open-definition (buffer-substring-no-properties p1 p2)))) | |||
| (defalias 'alchemist-goto-jump-back 'pop-tag-mark) | |||
| (provide 'alchemist-goto) | |||
| ;;; alchemist-goto.el ends here | |||
| @ -1,264 +0,0 @@ | |||
| ;;; 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 | |||
| @ -1,132 +0,0 @@ | |||
| ;;; alchemist.el --- Elixir tooling integration into Emacs | |||
| ;; Copyright © 2014-2015 Samuel Tonini | |||
| ;; | |||
| ;; Author: Samuel Tonini <tonini.samuel@gmail.com> | |||
| ;; URL: http://www.github.com/tonini/alchemist.el | |||
| ;; Version: 0.14.0-cvs | |||
| ;; Package-Requires: ((emacs "24")) | |||
| ;; Keywords: languages, mix, elixir, elixirc, hex | |||
| ;; 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: | |||
| ;; Alchemist integrate Elixir's tooling into Emacs | |||
| ;;; Code: | |||
| (defgroup alchemist nil | |||
| "Elixir Tooling Integration Into Emacs." | |||
| :prefix "alchemist-" | |||
| :group 'applications | |||
| :link '(url-link :tag "Github" "https://github.com/tonini/alchemist.el") | |||
| :link '(emacs-commentary-link :tag "Commentary" "alchemist")) | |||
| (require 'alchemist-utils) | |||
| (require 'alchemist-project) | |||
| (require 'alchemist-buffer) | |||
| (require 'alchemist-compile) | |||
| (require 'alchemist-execute) | |||
| (require 'alchemist-mix) | |||
| (require 'alchemist-hooks) | |||
| (require 'alchemist-help) | |||
| (require 'alchemist-complete) | |||
| (require 'alchemist-message) | |||
| (require 'alchemist-iex) | |||
| (require 'alchemist-eval) | |||
| (require 'alchemist-goto) | |||
| (eval-after-load 'company | |||
| '(progn | |||
| (require 'alchemist-company))) | |||
| (defun alchemist-mode-hook () | |||
| "Hook which enables `alchemist-mode'" | |||
| (alchemist-mode 1)) | |||
| (defvar alchemist--version "0.14.0-cvs") | |||
| ;;;###autoload | |||
| (defun alchemist-version (&optional show-version) | |||
| "Display Alchemist's version." | |||
| (interactive) | |||
| (message "Alchemist %s" (replace-regexp-in-string "-cvs" "snapshot" alchemist--version))) | |||
| (defvar alchemist-mode-map | |||
| (let ((map (make-sparse-keymap))) | |||
| (define-key map (kbd "C-c a t") 'alchemist-mix-test) | |||
| (define-key map (kbd "C-c a m t f") 'alchemist-mix-test-file) | |||
| (define-key map (kbd "C-c a m t b") 'alchemist-mix-test-this-buffer) | |||
| (define-key map (kbd "C-c a m t .") 'alchemist-mix-test-at-point) | |||
| (define-key map (kbd "C-c a c c") 'alchemist-compile) | |||
| (define-key map (kbd "C-c a c f") 'alchemist-compile-file) | |||
| (define-key map (kbd "C-c a c b") 'alchemist-compile-this-buffer) | |||
| (define-key map (kbd "C-c a e e") 'alchemist-execute) | |||
| (define-key map (kbd "C-c a e f") 'alchemist-execute-file) | |||
| (define-key map (kbd "C-c a e b") 'alchemist-execute-this-buffer) | |||
| (define-key map (kbd "C-c a h h") 'alchemist-help) | |||
| (define-key map (kbd "C-c a h e") 'alchemist-help-search-at-point) | |||
| (define-key map (kbd "C-c a h m") 'alchemist-help-search-marked-region) | |||
| (define-key map (kbd "C-c a p f") 'alchemist-project-find-test) | |||
| (define-key map (kbd "C-c a p t") 'alchemist-project-open-tests-for-current-file) | |||
| (define-key map (kbd "C-c a i i") 'alchemist-iex-run) | |||
| (define-key map (kbd "C-c a i p") 'alchemist-iex-project-run) | |||
| (define-key map (kbd "C-c a i l") 'alchemist-iex-send-current-line) | |||
| (define-key map (kbd "C-c a i c") 'alchemist-iex-send-current-line-and-go) | |||
| (define-key map (kbd "C-c a i r") 'alchemist-iex-send-region) | |||
| (define-key map (kbd "C-c a i m") 'alchemist-iex-send-region-and-go) | |||
| (define-key map (kbd "C-c a i b") 'alchemist-iex-compile-this-buffer) | |||
| (define-key map (kbd "C-c a v l") 'alchemist-eval-current-line) | |||
| (define-key map (kbd "C-c a v k") 'alchemist-eval-print-current-line) | |||
| (define-key map (kbd "C-c a v j") 'alchemist-eval-quoted-current-line) | |||
| (define-key map (kbd "C-c a v h") 'alchemist-eval-print-quoted-current-line) | |||
| (define-key map (kbd "C-c a v o") 'alchemist-eval-region) | |||
| (define-key map (kbd "C-c a v i") 'alchemist-eval-print-region) | |||
| (define-key map (kbd "C-c a v u") 'alchemist-eval-quoted-region) | |||
| (define-key map (kbd "C-c a v y") 'alchemist-eval-print-quoted-region) | |||
| (define-key map (kbd "C-c a v q") 'alchemist-eval-buffer) | |||
| (define-key map (kbd "C-c a v w") 'alchemist-eval-print-buffer) | |||
| (define-key map (kbd "C-c a v e") 'alchemist-eval-quoted-buffer) | |||
| (define-key map (kbd "C-c a v r") 'alchemist-eval-print-quoted-buffer) | |||
| (define-key map (kbd "M-.") 'alchemist-goto-definition-at-point) | |||
| (define-key map (kbd "M-,") 'alchemist-goto-jump-back) | |||
| map) | |||
| "The keymap used when `alchemist-mode' is active.") | |||
| ;;;###autoload | |||
| (define-minor-mode alchemist-mode | |||
| "Toggle alchemist mode. | |||
| Key bindings: | |||
| \\{alchemist-mode-map}" | |||
| nil | |||
| ;; The indicator for the mode line. | |||
| " alchemist" | |||
| :group 'alchemist | |||
| :global nil | |||
| :keymap 'alchemist-mode-map | |||
| (cond (alchemist-mode | |||
| (alchemist-buffer-initialize-modeline)) | |||
| (t | |||
| (alchemist-buffer-reset-modeline)))) | |||
| (add-hook 'elixir-mode-hook 'alchemist-mode-hook) | |||
| (provide 'alchemist) | |||
| ;;; alchemist.el ends here | |||
| @ -0,0 +1,336 @@ | |||
| ;;; alchemist-goto.el --- Functionality to jump modules and function definitions | |||
| ;; Copyright © 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 to jump modules and function definitions | |||
| ;;; Code: | |||
| (require 'etags) | |||
| (defgroup alchemist-goto nil | |||
| "Functionality to jump modules and function definitions." | |||
| :prefix "alchemist-goto-" | |||
| :group 'alchemist) | |||
| ;; Variables | |||
| (defcustom alchemist-goto-erlang-source-dir "" | |||
| "Path to the erlang source code." | |||
| :type 'string | |||
| :group 'alchemist-goto) | |||
| (defcustom alchemist-goto-elixir-source-dir "" | |||
| "Path to the elixir source code." | |||
| :type 'string | |||
| :group 'alchemist-goto) | |||
| (defvar alchemist-goto--symbol-list '()) | |||
| (defvar alchemist-goto--symbol-name-and-pos '()) | |||
| (defvar alchemist-goto--symbol-list-bare '()) | |||
| (defvar alchemist-goto--symbol-name-and-pos-bare '()) | |||
| ;; Private functions | |||
| (defun alchemist-goto--current-module-name () | |||
| "Searches backward in the current buffer until a module | |||
| declaration has been found." | |||
| (save-excursion | |||
| (let ((found-flag-p nil) | |||
| (module-name "")) | |||
| (save-match-data | |||
| (while (and (not found-flag-p) | |||
| (re-search-backward "defmodule \\([A-Za-z\._]+\\)\s+" nil t)) | |||
| (when (not (alchemist-goto--string-at-point-p)) | |||
| (setq module-name (match-string 1)) | |||
| (setq found-flag-p t)) | |||
| (when (equal 1 (line-number-at-pos (point))) | |||
| (setq found-flag-p t))) | |||
| module-name)))) | |||
| (defun alchemist-goto--use-modules-in-the-current-module-context () | |||
| (let ((modules '()) | |||
| (context (alchemist-goto--current-module-name))) | |||
| (save-excursion | |||
| (while (re-search-backward "^\s+use\s+\\([A-Za-z0-9\.]+\\)" nil t) | |||
| (if (and (match-string 1) | |||
| (not (alchemist-goto--string-at-point-p)) | |||
| (equal context (alchemist-goto--current-module-name))) | |||
| (setq modules (add-to-list 'modules (substring-no-properties (match-string 1)))) | |||
| )) | |||
| modules))) | |||
| (defun alchemist-goto--import-modules-in-the-current-module-context () | |||
| (let ((modules '()) | |||
| (context (alchemist-goto--current-module-name))) | |||
| (save-excursion | |||
| (while (re-search-backward "^\s+import\s+\\([A-Za-z0-9\.]+\\)" nil t) | |||
| (if (and (match-string 1) | |||
| (not (alchemist-goto--string-at-point-p)) | |||
| (equal context (alchemist-goto--current-module-name))) | |||
| (setq modules (add-to-list 'modules (substring-no-properties (match-string 1)))) | |||
| )) | |||
| modules))) | |||
| (defun alchemist-goto--extract-module (code) | |||
| "Extract module from CODE." | |||
| (let* ((parts (split-string code "\\.")) | |||
| (function (car (last parts))) | |||
| (case-fold-search nil)) | |||
| (when (string-match-p "^[a-z_\?!]+" function) | |||
| (delete function parts)) | |||
| (unless (string-match-p "^[a-z_\?!]+" (car parts)) | |||
| (replace-regexp-in-string "\\.$" "" (mapconcat 'concat parts "."))))) | |||
| (defun alchemist-goto--extract-function (code) | |||
| "Extract function from CODE." | |||
| (let* ((parts (split-string code "\\.")) | |||
| (function (car (last parts))) | |||
| (case-fold-search nil)) | |||
| (when (and function | |||
| (string-match-p "^[a-z_\?!]+" function)) | |||
| function))) | |||
| (defun alchemist-goto--build-elixir-ex-core-file (file) | |||
| (when (string-match "\\/\\(lib\\/.+\\/lib\\)\\/.+\.ex$" file) | |||
| (let* ((file (substring-no-properties file (match-beginning 1))) | |||
| (source-directory (expand-file-name alchemist-goto-elixir-source-dir))) | |||
| (concat source-directory file)))) | |||
| (defun alchemist-goto--build-elixir-erl-core-file (file) | |||
| (when (string-match "\\/\\(lib\\/.+\\/src\\)\\/.+\.erl$" file) | |||
| (let* ((file (substring-no-properties file (match-beginning 1))) | |||
| (source-directory (expand-file-name alchemist-goto-elixir-source-dir))) | |||
| (concat source-directory file)))) | |||
| (defun alchemist-goto--build-erlang-core-file (file) | |||
| (when (string-match "\\/\\(lib\\/.+\\/src\\)\\/.+\.erl$" file) | |||
| (let* ((file (substring-no-properties file (match-beginning 1))) | |||
| (source-directory (expand-file-name alchemist-goto-erlang-source-dir))) | |||
| (concat source-directory file)))) | |||
| (defun alchemist-goto--elixir-file-p (file) | |||
| (string-match-p "\\.ex\\(s\\)?$" file)) | |||
| (defun alchemist-goto--erlang-file-p (file) | |||
| (string-match-p "\\.erl$" file)) | |||
| (defun alchemist-goto--get-full-path-of-alias (module) | |||
| (if (not (alchemist-utils--empty-string-p module)) | |||
| (let* ((aliases (mapcar (lambda (m) | |||
| (when (string-match-p (format "^%s" (car (cdr m))) module) | |||
| (replace-regexp-in-string (format "^%s" (car (cdr m))) (car m) module t))) | |||
| (alchemist-goto--alises-of-current-buffer))) | |||
| (aliases (delete nil aliases))) | |||
| (if aliases | |||
| (car aliases) | |||
| module)))) | |||
| (defun alchemist-goto--string-at-point-p (&optional complete) | |||
| "Return non-nil if cursor is at a string." | |||
| (save-excursion | |||
| (or (and (nth 3 (save-excursion | |||
| (let ((pos (point))) | |||
| (when complete | |||
| (end-of-buffer)) | |||
| (parse-partial-sexp 1 pos)))) | |||
| (nth 8 (save-excursion | |||
| (let ((pos (point))) | |||
| (when complete | |||
| (end-of-buffer)) | |||
| (parse-partial-sexp 1 pos))))) | |||
| (and (looking-at "\"\"\"\\|'''\\|\"\\|\'") | |||
| (match-beginning 0))))) | |||
| (defun alchemist-goto--symbol-definition-p (symbol) | |||
| (alchemist-goto--fetch-symbol-definitions) | |||
| (if (member symbol alchemist-goto--symbol-list-bare) | |||
| t | |||
| nil)) | |||
| (defun alchemist-goto--goto-symbol (symbol) | |||
| (let ((position (cdr (assoc symbol alchemist-goto--symbol-name-and-pos-bare)))) | |||
| (goto-char (if (overlayp position) (overlay-start position) position)))) | |||
| (defun alchemist-goto-list-symbol-definitions () | |||
| "List all symbol definitions in the current file like functions/macros/modules. | |||
| It will jump to the position of the symbol definition after selection." | |||
| (interactive) | |||
| (alchemist-goto--fetch-symbol-definitions) | |||
| (ring-insert find-tag-marker-ring (point-marker)) | |||
| (let* ((selected-def (completing-read "Symbol definitions:" alchemist-goto--symbol-list)) | |||
| (position (cdr (assoc selected-def alchemist-goto--symbol-name-and-pos)))) | |||
| (goto-char (if (overlayp position) (overlay-start position) position)))) | |||
| (defun alchemist-goto--fetch-symbol-definitions () | |||
| (alchemist-goto--search-for-symbols "^\\s-*\\(defp?\\|defmacrop?\\|defmodule\\)\s.*")) | |||
| (defface alchemist-goto--def-face | |||
| '((t (:inherit font-lock-constant-face))) | |||
| "" | |||
| :group 'alchemist-goto) | |||
| (defface alchemist-goto--name-face | |||
| '((t (:bold t))) | |||
| "" | |||
| :group 'alchemist-goto) | |||
| (defvar alchemist-goto--symbol-def-extract-regex | |||
| "^\\s-*\\(defp?\\|defmacrop?\\|defmodule\\)[ \n\t]+\\([a-z_\?!]+\\)\\(.*\\)\\(do\\|\n\\)?$") | |||
| (defun alchemist-goto--extract-symbol (str) | |||
| (save-match-data | |||
| (when (string-match alchemist-goto--symbol-def-extract-regex str) | |||
| (let ((type (substring str (match-beginning 1) (match-end 1))) | |||
| (name (substring str (match-beginning 2) (match-end 2))) | |||
| (arguments (substring str (match-beginning 3) (match-end 3)))) | |||
| (concat | |||
| (propertize type | |||
| 'face 'alchemist-goto--def-face) | |||
| " " | |||
| (propertize name | |||
| 'face 'alchemist-goto--name-face) | |||
| (replace-regexp-in-string ",?\s+do:.*$" "" (replace-regexp-in-string "\s+do$" "" arguments))))))) | |||
| (defun alchemist-goto--extract-symbol-bare (str) | |||
| (save-match-data | |||
| (when (string-match alchemist-goto--symbol-def-extract-regex str) | |||
| (let ((type (substring str (match-beginning 1) (match-end 1))) | |||
| (name (substring str (match-beginning 2) (match-end 2))) | |||
| (arguments (substring str (match-beginning 3) (match-end 3)))) | |||
| name)))) | |||
| (defun alchemist-goto--get-symbol-from-position (position) | |||
| (with-current-buffer (buffer-name) | |||
| (save-excursion | |||
| (goto-char position) | |||
| (end-of-line) | |||
| (let* ((end-position (point)) | |||
| (line (buffer-substring-no-properties position end-position))) | |||
| (alchemist-goto--extract-symbol line))))) | |||
| (defun alchemist-goto--get-symbol-from-position-bare (position) | |||
| (with-current-buffer (buffer-name) | |||
| (save-excursion | |||
| (goto-char position) | |||
| (end-of-line) | |||
| (let* ((end-position (point)) | |||
| (line (buffer-substring-no-properties position end-position))) | |||
| (alchemist-goto--extract-symbol-bare line))))) | |||
| (defun alchemist-goto--search-for-symbols (regex) | |||
| (setq alchemist-goto--symbol-list '()) | |||
| (setq alchemist-goto--symbol-name-and-pos '()) | |||
| (with-current-buffer (buffer-name) | |||
| (save-excursion | |||
| (goto-char (point-max)) | |||
| (goto-char (point-min)) | |||
| (let () | |||
| (save-match-data | |||
| (while (re-search-forward regex nil t) | |||
| (when (not (alchemist-goto--string-at-point-p t)) | |||
| (when (alchemist-goto--get-symbol-from-position (car (match-data))) | |||
| (let* ((position (car (match-data))) | |||
| (symbol (alchemist-goto--get-symbol-from-position position)) | |||
| (symbol-bare (alchemist-goto--get-symbol-from-position-bare position))) | |||
| (setq alchemist-goto--symbol-list (append alchemist-goto--symbol-list (list symbol))) | |||
| (setq alchemist-goto--symbol-name-and-pos (append alchemist-goto--symbol-name-and-pos (list (cons symbol position)))) | |||
| (setq alchemist-goto--symbol-list-bare (append alchemist-goto--symbol-list-bare (list symbol-bare))) | |||
| (setq alchemist-goto--symbol-name-and-pos-bare (append alchemist-goto--symbol-name-and-pos-bare (list (cons symbol-bare position))))))))))))) | |||
| (defun alchemist-goto--open-definition (expr) | |||
| (let* ((module (alchemist-goto--extract-module expr)) | |||
| (module (alchemist-goto--get-full-path-of-alias module)) | |||
| (module (if module module "nil")) | |||
| (function (alchemist-goto--extract-function expr)) | |||
| (function (if function function "\"\""))) | |||
| (ring-insert find-tag-marker-ring (point-marker)) | |||
| (cond | |||
| ((and (string-equal module "nil") | |||
| (string-equal major-mode "elixir-mode") | |||
| (alchemist-goto--symbol-definition-p function)) | |||
| (alchemist-goto--goto-symbol function)) | |||
| (t (alchemist-server-goto module function expr) | |||
| )))) | |||
| (defun alchemist-goto--open-file (file module function) | |||
| (let* ((buf (find-file-noselect file))) | |||
| (switch-to-buffer buf) | |||
| (beginning-of-buffer) | |||
| (cond ((alchemist-goto--elixir-file-p file) | |||
| (alchemist-goto--jump-to-elixir-source module function)) | |||
| ((alchemist-goto--erlang-file-p file) | |||
| (alchemist-goto--jump-to-erlang-source module function))))) | |||
| (defun alchemist-gogo--symbol-definition-regex (symbol) | |||
| (format "^\s+\\(defp?\s+%s\(?\\|defmacrop?\s+%s\(?\\)" symbol symbol)) | |||
| (defun alchemist-goto--jump-to-elixir-source (module function) | |||
| (let ((function (replace-regexp-in-string "\?" "\\?" function))) | |||
| (when (re-search-forward (alchemist-gogo--symbol-definition-regex function) nil t) | |||
| (goto-char (match-beginning 0))) | |||
| (when (re-search-forward (format "\\(defmodule\\|defimpl\\|defprotocol\\)\s+%s\s+do" module) nil t) | |||
| (goto-char (match-beginning 0))))) | |||
| (defun alchemist-goto--jump-to-erlang-source (module function) | |||
| (when (re-search-forward (format "\\(^%s\(\\)" function) nil t) | |||
| (goto-char (match-beginning 0))) | |||
| (when (re-search-forward (format "\\(^-module\(%s\)\\)" (substring module 1)) nil t) | |||
| (goto-char (match-beginning 0)))) | |||
| (defun alchemist-goto--context-exists-p () | |||
| (interactive) | |||
| (save-excursion | |||
| (goto-char (point-min)) | |||
| (if (re-search-forward "defmodule \\([A-Za-z\._]+\\)\s+" nil t) | |||
| t | |||
| nil))) | |||
| (defun alchemist-goto--alises-of-current-buffer () | |||
| (let* ((aliases '())) | |||
| (save-excursion | |||
| (goto-char (point-min)) | |||
| (while (re-search-forward "^\s+alias\s+\\([-:_A-Za-z0-9,\.\?!\]+\\)\\(\s*,\s*as:\s*\\)?\\([-_A-Za-z0-9,\.\?!\]+\\)?\n" nil t) | |||
| (let* ((alias (match-string 1)) | |||
| (as (if (match-string 3) (match-string 3) nil)) | |||
| (as (if as as (car (last (split-string alias "\\.")))))) | |||
| (setq aliases (append aliases (list (list alias as))))))) | |||
| aliases)) | |||
| ;; Public functions | |||
| (defun alchemist-goto-definition-at-point () | |||
| "Jump to the elixir expression definition at point." | |||
| (interactive) | |||
| (let (p1 p2) | |||
| (skip-chars-backward "-_A-Za-z0-9.?!:") | |||
| (setq p1 (point)) | |||
| (skip-chars-forward "-_A-Za-z0-9.?!:") | |||
| (setq p2 (point)) | |||
| (alchemist-goto--open-definition (buffer-substring-no-properties p1 p2)))) | |||
| (defalias 'alchemist-goto-jump-back 'pop-tag-mark) | |||
| (provide 'alchemist-goto) | |||
| ;;; alchemist-goto.el ends here | |||
| @ -0,0 +1,204 @@ | |||
| ;;; 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-buffer-name "*elixir 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.") | |||
| ;; 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--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--execute (search) | |||
| (alchemist-server-help-with-complete search)) | |||
| (defun alchemist-help--execute-without-complete (search) | |||
| (alchemist-server-help-without-complete search)) | |||
| (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) | |||
| (let ((default-directory (if (alchemist-project-root) | |||
| (alchemist-project-root) | |||
| default-directory))) | |||
| (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 | |||
| (if (get-buffer alchemist-help-buffer-name) | |||
| (kill-buffer alchemist-help-buffer-name)) | |||
| (pop-to-buffer alchemist-help-buffer-name) | |||
| (setq buffer-undo-list nil) | |||
| (let ((inhibit-read-only t) | |||
| (buffer-undo-list 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)) | |||
| (read-only-mode 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) | |||
| (let* ((expr (alchemist-help--exp-at-point)) | |||
| (module (alchemist-goto--extract-module expr)) | |||
| (module (alchemist-goto--get-full-path-of-alias module)) | |||
| (module (if module module "")) | |||
| (function (alchemist-goto--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)))) | |||
| (alchemist-help--execute 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." | |||
| (interactive "r") | |||
| (let* ((expr (filter-buffer-substring begin end)) | |||
| (module (alchemist-goto--extract-module expr)) | |||
| (module (alchemist-goto--get-full-path-of-alias module)) | |||
| (module (if module module "")) | |||
| (function (alchemist-goto--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)))) | |||
| (alchemist-help--execute expr))) | |||
| (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)) | |||
| (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 "m") #'alchemist-help-search-marked-region) | |||
| (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) | |||
| (defun alchemist-help () | |||
| "Load Elixir documentation for SEARCH." | |||
| (interactive) | |||
| (alchemist-server-help)) | |||
| (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 | |||
| @ -0,0 +1,308 @@ | |||
| ;;; alchemist-server.el --- -*- lexical-binding: t -*- | |||
| ;; Copyright © 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: | |||
| ;; | |||
| ;;; Code: | |||
| (defvar alchemist-server | |||
| (concat (file-name-directory load-file-name) "server/server.exs") | |||
| "Script file with alchemist server.") | |||
| (defvar alchemist-server--processes '()) | |||
| (defvar alchemist-server--env "dev") | |||
| (defvar alchemist-server-command | |||
| (format "elixir %s %s" alchemist-server alchemist-server--env)) | |||
| (defun alchemist-server-start (env) | |||
| "Start alchemist server for the current mix project in specific ENV." | |||
| (interactive (list | |||
| (completing-read (format "(Alchemist-Server) run in environment: (default: %s) " alchemist-server--env) | |||
| alchemist-mix--envs nil nil nil))) | |||
| (when (alchemist-server--process-p) | |||
| (kill-process (alchemist-server--process))) | |||
| (alchemist-server--start-with-env env)) | |||
| (defun alchemist-server--start () | |||
| (unless (alchemist-server--process-p) | |||
| (alchemist-server--start-with-env alchemist-server--env))) | |||
| (defun alchemist-server--start-with-env (env) | |||
| (let* ((process-name (alchemist-server--process-name)) | |||
| (default-directory (if (string= process-name "alchemist-server") | |||
| default-directory | |||
| process-name)) | |||
| (server-command (format "elixir %s %s" alchemist-server env)) | |||
| (process (start-process-shell-command process-name "*alchemist-server*" server-command))) | |||
| (set-process-query-on-exit-flag process nil) | |||
| (alchemist-server--store-process process))) | |||
| (defun alchemist-server--store-process (process) | |||
| (let ((process-name (alchemist-server--process-name))) | |||
| (if (cdr (assoc process-name alchemist-server--processes)) | |||
| (setq alchemist-server--processes | |||
| (delq (assoc process-name alchemist-server--processes) alchemist-server--processes))) | |||
| (add-to-list 'alchemist-server--processes (cons process-name process)))) | |||
| (defun alchemist-server--process-p () | |||
| (process-live-p (alchemist-server--process))) | |||
| (defun alchemist-server--process () | |||
| (cdr (assoc (alchemist-server--process-name) alchemist-server--processes))) | |||
| (defun alchemist-server--process-name () | |||
| (let* ((process-name (alchemist-project-root)) | |||
| (process-name (if process-name | |||
| process-name | |||
| "alchemist-server"))) | |||
| process-name)) | |||
| (defun alchemist-server-eval-filter (process output) | |||
| (setq alchemist-server--output (cons output alchemist-server--output)) | |||
| (if (string-match "END-OF-EVAL$" output) | |||
| (let* ((output (apply #'concat (reverse alchemist-server--output))) | |||
| (output (replace-regexp-in-string "END-OF-EVAL" "" output)) | |||
| (output (replace-regexp-in-string "\n$" "" output))) | |||
| (funcall alchemist-server-eval-callback output)))) | |||
| (defun alchemist-server-eval-quoted-filter (process output) | |||
| (setq alchemist-server--output (cons output alchemist-server--output)) | |||
| (if (string-match "END-OF-QUOTE$" output) | |||
| (let* ((output (apply #'concat (reverse alchemist-server--output))) | |||
| (output (replace-regexp-in-string "END-OF-QUOTE" "" output)) | |||
| (output (replace-regexp-in-string "\n$" "" output))) | |||
| (funcall alchemist-server-eval-callback output)))) | |||
| (defun alchemist-server-doc-filter (process output) | |||
| (setq alchemist-server--output (cons output alchemist-server--output)) | |||
| (if (string-match "END-OF-DOC$" output) | |||
| (let* ((string (apply #'concat (reverse alchemist-server--output))) | |||
| (string (replace-regexp-in-string "END-OF-DOC$" "" string))) | |||
| (alchemist-help--initialize-buffer string)))) | |||
| (defun alchemist-server-complete-canidates-filter (process output) | |||
| (setq alchemist-server--output (cons output alchemist-server--output)) | |||
| (unless (alchemist-utils--empty-string-p output) | |||
| (if (string-match "END-OF-COMPLETE$" output) | |||
| (let* ((string (apply #'concat (reverse alchemist-server--output))) | |||
| (string (replace-regexp-in-string "END-OF-COMPLETE$" "" string)) | |||
| (candidates (if (not (alchemist-utils--empty-string-p string)) | |||
| (alchemist-complete--output-to-list | |||
| (alchemist--utils-clear-ansi-sequences string)) | |||
| '())) | |||
| (candidates (if candidates | |||
| (remove-duplicates candidates) | |||
| '())) | |||
| (candidates (if candidates | |||
| (alchemist-complete--build-candidates candidates) | |||
| '()))) | |||
| (alchemist-complete--serve-candidates-to-company candidates))))) | |||
| (defun alchemist-server-complete-canidates-filter-with-context (process output) | |||
| (setq alchemist-server--output (cons output alchemist-server--output)) | |||
| (if (string-match "END-OF-COMPLETE-WITH-CONTEXT$" output) | |||
| (let* ((string (apply #'concat (reverse alchemist-server--output))) | |||
| (string (replace-regexp-in-string "END-OF-COMPLETE-WITH-CONTEXT$" "" string)) | |||
| (candidates (if (not (alchemist-utils--empty-string-p string)) | |||
| (alchemist-complete--output-to-list | |||
| (alchemist--utils-clear-ansi-sequences string)) | |||
| '())) | |||
| (candidates (if candidates | |||
| (remove-duplicates candidates) | |||
| '())) | |||
| (candidates (if candidates | |||
| (alchemist-complete--build-candidates candidates) | |||
| '()))) | |||
| (alchemist-complete--serve-candidates-to-company candidates)))) | |||
| (defun alchemist-server-complete-filter (process output) | |||
| (with-local-quit | |||
| (setq alchemist-server--output (cons output alchemist-server--output)) | |||
| (if (string-match "END-OF-COMPLETE$" output) | |||
| (let* ((string (apply #'concat (reverse alchemist-server--output))) | |||
| (string (replace-regexp-in-string "END-OF-COMPLETE$" "" string)) | |||
| (candidates (alchemist-complete--output-to-list | |||
| (alchemist--utils-clear-ansi-sequences string)))) | |||
| (funcall alchemist-server-help-callback candidates))))) | |||
| (defun alchemist-server-help-complete-modules-filter (process output) | |||
| (with-local-quit | |||
| (setq alchemist-server--output (cons output alchemist-server--output)) | |||
| (if (string-match "END-OF-MODULES$" output) | |||
| (let* ((output (apply #'concat (reverse alchemist-server--output))) | |||
| (modules (alchemist-help--elixir-modules-to-list output)) | |||
| (search (completing-read | |||
| "Elixir help: " | |||
| modules | |||
| nil | |||
| nil | |||
| nil))) | |||
| (alchemist-help--execute (if (string-match-p "\\.$" search) | |||
| search | |||
| (concat search "."))))))) | |||
| (defun alchemist-server-goto-filter (process output) | |||
| (setq alchemist-server--output (cons output alchemist-server--output)) | |||
| (if (string-match "END-OF-SOURCE$" output) | |||
| (let* ((output (apply #'concat (reverse alchemist-server--output))) | |||
| (output (replace-regexp-in-string "END-OF-SOURCE" "" output)) | |||
| (output (replace-regexp-in-string "\n" "" output)) | |||
| (file (replace-regexp-in-string "source-file-path:" "" output))) | |||
| (funcall alchemist-server-goto-callback file)))) | |||
| (defun alchemist-server-goto (module function expr) | |||
| (setq alchemist-server--output nil) | |||
| (alchemist-server--start) | |||
| (setq alchemist-server-goto-callback (lambda (file) | |||
| (cond ((alchemist-utils--empty-string-p file) | |||
| (message "Don't know how to find: %s" expr)) | |||
| ((file-exists-p file) | |||
| (alchemist-goto--open-file file module function)) | |||
| ((alchemist-goto--elixir-file-p file) | |||
| (let* ((elixir-source-file (alchemist-goto--build-elixir-ex-core-file file))) | |||
| (if (file-exists-p elixir-source-file) | |||
| (alchemist-goto--open-file elixir-source-file module function) | |||
| (message "Don't know how to find: %s" expr)))) | |||
| ((alchemist-goto--erlang-file-p file) | |||
| (let* ((elixir-source-file (alchemist-goto--build-elixir-erl-core-file file)) | |||
| (erlang-source-file (alchemist-goto--build-erlang-core-file file))) | |||
| (cond ((file-exists-p elixir-source-file) | |||
| (alchemist-goto--open-file elixir-source-file module function)) | |||
| ((file-exists-p erlang-source-file) | |||
| (alchemist-goto--open-file erlang-source-file module function)) | |||
| (t | |||
| (message "Don't know how to find: %s" expr))))) | |||
| (t | |||
| (pop-tag-mark) | |||
| (message "Don't know how to find: %s" expr))))) | |||
| (set-process-filter (alchemist-server--process) #'alchemist-server-goto-filter) | |||
| (process-send-string (alchemist-server--process) (format "SOURCE %s,%s\n" module function))) | |||
| (defun alchemist-server-help () | |||
| (setq alchemist-server--output nil) | |||
| (alchemist-server--start) | |||
| (set-process-filter (alchemist-server--process) #'alchemist-server-help-complete-modules-filter) | |||
| (process-send-string (alchemist-server--process) "MODULES\n")) | |||
| (defun alchemist-server-eval (exp) | |||
| (setq alchemist-server--output nil) | |||
| (alchemist-server--start) | |||
| (setq alchemist-server-eval-callback (lambda (string) | |||
| (message "%s" string))) | |||
| (set-process-filter (alchemist-server--process) #'alchemist-server-eval-filter) | |||
| (process-send-string (alchemist-server--process) (format "EVAL %s\n" exp))) | |||
| (defun alchemist-server-eval-and-insert (exp) | |||
| (setq alchemist-server--output nil) | |||
| (alchemist-server--start) | |||
| (setq alchemist-server-eval-callback (lambda (string) | |||
| (alchemist-eval--insert string))) | |||
| (set-process-filter (alchemist-server--process) #'alchemist-server-eval-filter) | |||
| (process-send-string (alchemist-server--process) (format "EVAL %s\n" exp))) | |||
| (defun alchemist-server-eval-quote (exp) | |||
| (setq alchemist-server--output nil) | |||
| (alchemist-server--start) | |||
| (setq alchemist-server-eval-callback (lambda (string) | |||
| (message "%s" string))) | |||
| (set-process-filter (alchemist-server--process) #'alchemist-server-eval-quoted-filter) | |||
| (process-send-string (alchemist-server--process) (format "QUOTE %s\n" exp))) | |||
| (defun alchemist-server-eval-quote-and-insert (exp) | |||
| (setq alchemist-server--output nil) | |||
| (alchemist-server--start) | |||
| (setq alchemist-server-eval-callback (lambda (string) | |||
| (alchemist-eval--insert string))) | |||
| (set-process-filter (alchemist-server--process) #'alchemist-server-eval-quoted-filter) | |||
| (process-send-string (alchemist-server--process) (format "QUOTE %s\n" exp))) | |||
| (defun alchemist-server-complete-candidates (exp) | |||
| (setq alchemist-server--output nil) | |||
| (setq alchemist-server--last-completion-exp exp) | |||
| (alchemist-server--start) | |||
| (if (or (equal major-mode 'alchemist-iex-mode) | |||
| (not (alchemist-goto--context-exists-p))) | |||
| (alchemist-server--iex-complete exp) | |||
| (alchemist-server--complete-with-context exp))) | |||
| (defun alchemist-server--complete-with-context (exp) | |||
| (let* ((module (alchemist-goto--current-module-name)) | |||
| (modules '()) | |||
| (aliases (mapcar (lambda (a) | |||
| (if (not (or (alchemist-utils--empty-string-p (replace-regexp-in-string "\\.$" "" (car (cdr a)))) | |||
| (string= (replace-regexp-in-string "\\.$" "" (car (cdr a))) | |||
| (replace-regexp-in-string "\\.$" "" (car a))))) | |||
| (format "{%s, %s}" | |||
| (if (alchemist-utils--empty-string-p (replace-regexp-in-string "\\.$" "" (car (cdr a)))) | |||
| (replace-regexp-in-string "\\.$" "" (car a)) | |||
| (replace-regexp-in-string "\\.$" "" (car (cdr a)))) | |||
| (replace-regexp-in-string "\\.$" "" (car a)) | |||
| ))) (alchemist-goto--alises-of-current-buffer))) | |||
| (use-modules (alchemist-goto--use-modules-in-the-current-module-context)) | |||
| (import-modules (alchemist-goto--import-modules-in-the-current-module-context))) | |||
| (if (not (alchemist-utils--empty-string-p module)) | |||
| (push module modules)) | |||
| (push use-modules modules) | |||
| (push import-modules modules) | |||
| (if (not modules) | |||
| (progn | |||
| (set-process-filter (alchemist-server--process) #'alchemist-server-complete-canidates-filter) | |||
| (process-send-string (alchemist-server--process) (format "COMPLETE %s\n" exp))) | |||
| (progn | |||
| (set-process-filter (alchemist-server--process) #'alchemist-server-complete-canidates-filter-with-context) | |||
| (process-send-string (alchemist-server--process) (format "COMPLETE-WITH-CONTEXT %s;[%s];%s\n" | |||
| exp | |||
| (mapconcat #'identity (alchemist-utils--flatten modules) ",") | |||
| (format "[%s]" (if (mapconcat #'identity aliases ",") | |||
| (mapconcat #'identity aliases ",") | |||
| "")))))))) | |||
| (defun alchemist-server--iex-complete (exp) | |||
| (set-process-filter (alchemist-server--process) #'alchemist-server-complete-canidates-filter) | |||
| (process-send-string (alchemist-server--process) (format "COMPLETE %s\n" exp))) | |||
| (defun alchemist-server-help-with-complete (search) | |||
| (setq alchemist-server--output nil) | |||
| (alchemist-server--start) | |||
| (setq alchemist-server-help-callback (lambda (candidates) | |||
| (if candidates | |||
| (let* ((search (alchemist-complete--completing-prompt search candidates))) | |||
| (alchemist-server-help-without-complete search)) | |||
| (message "No documentation found for '%s'" search)) | |||
| )) | |||
| (set-process-filter (alchemist-server--process) #'alchemist-server-complete-filter) | |||
| (process-send-string (alchemist-server--process) (format "COMPLETE %s\n" search))) | |||
| (defun alchemist-server-help-without-complete (search) | |||
| (setq alchemist-help-current-search-text search) | |||
| (setq alchemist-server--output nil) | |||
| (alchemist-server--start) | |||
| (setq alchemist-server--output nil) | |||
| (set-process-filter (alchemist-server--process) #'alchemist-server-doc-filter) | |||
| (process-send-string (alchemist-server--process) (format "DOC %s\n" search))) | |||
| (provide 'alchemist-server) | |||
| ;;; alchemist-server.el ends here | |||
| @ -0,0 +1,169 @@ | |||
| ;;; alchemist-test-mode.el --- Minor mode for Elixir test files. | |||
| ;; Copyright © 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: | |||
| ;; Minor mode for Elixir test files. | |||
| ;;; Code: | |||
| (defgroup alchemist-test-mode nil | |||
| "Minor mode for Elixir ExUnit files." | |||
| :prefix "alchemist-test-mode-" | |||
| :group 'alchemist) | |||
| ;; Variables | |||
| (defvar alchemist-test-mode-buffer-name "*alchemist-test-report*" | |||
| "Name of the test report buffer.") | |||
| (defcustom alchemist-test-mode-highlight-tests t | |||
| "Non-nil means that specific functions for testing will | |||
| be highlighted with more significant font faces." | |||
| :type 'boolean | |||
| :group 'alchemist-test-mode) | |||
| (defvar alchemist-test-at-point #'alchemist-mix-test-at-point) | |||
| (defvar alchemist-test-this-buffer #'alchemist-mix-test-this-buffer) | |||
| (defvar alchemist-test #'alchemist-mix-test) | |||
| (defvar alchemist-test-file #'alchemist-mix-test-file) | |||
| (defvar alchemist-test-jump-to-previous-test #'alchemist-test-mode-jump-to-previous-test) | |||
| (defvar alchemist-test-jump-to-next-test #'alchemist-test-mode-jump-to-next-test) | |||
| (defvar alchemist-test-list-tests #'alchemist-test-mode-list-tests) | |||
| (defvar alchemist-test-mode-map | |||
| (let ((map (make-sparse-keymap))) | |||
| (define-key map (kbd "C-c , s") alchemist-test-at-point) | |||
| (define-key map (kbd "C-c , v") alchemist-test-this-buffer) | |||
| (define-key map (kbd "C-c , a") alchemist-test) | |||
| (define-key map (kbd "C-c , f") alchemist-test-file) | |||
| (define-key map (kbd "C-c , p") alchemist-test-jump-to-previous-test) | |||
| (define-key map (kbd "C-c , n") alchemist-test-jump-to-next-test) | |||
| (define-key map (kbd "C-c , l") alchemist-test-list-tests) | |||
| map) | |||
| "Keymap for `alchemist-test-mode'.") | |||
| (let ((whitespace-opt "[[:space:]]*") | |||
| (whitespace "[[:space:]]+")) | |||
| (setq alchemist-test-mode--test-regex | |||
| (concat | |||
| "\\(^" whitespace-opt "test" whitespace "\\(?10:.+\\)" whitespace "do" whitespace-opt "$" | |||
| "\\|" | |||
| whitespace " [0-9]+) test .+\\)"))) | |||
| ;; Private functions | |||
| (defun alchemist-test-mode--buffer-contains-tests-p () | |||
| "Return nil if the current buffer contains no tests, non-nil if it does." | |||
| (save-excursion | |||
| (save-match-data | |||
| (beginning-of-buffer) | |||
| (re-search-forward alchemist-test-mode--test-regex nil t)))) | |||
| (defun alchemist-test-mode--jump-to-test (search-fn reset-fn) | |||
| "Move the point to the next/previous test, based on `search-fn' (which is the | |||
| function that searches for the next test, can be re-search-forward or | |||
| re-search-backward) and `reset-fn' (which is used when wrapping at the | |||
| beginning/end of the buffer if no results were found)." | |||
| (when (alchemist-test-mode--buffer-contains-tests-p) | |||
| (save-match-data | |||
| (unless (funcall search-fn alchemist-test-mode--test-regex nil t) | |||
| (funcall reset-fn) | |||
| (funcall search-fn alchemist-test-mode--test-regex nil t)) | |||
| (back-to-indentation)))) | |||
| (defun alchemist-test-mode--tests-in-buffer () | |||
| "Return an alist of tests in this buffer. | |||
| The keys in the list are the test names (e.g., the string passed to the test/2 | |||
| macro) while the values are the position at which the test matched." | |||
| (save-match-data | |||
| (save-excursion | |||
| (beginning-of-buffer) | |||
| (let ((tests '())) | |||
| (while (re-search-forward alchemist-test-mode--test-regex nil t) | |||
| (let* ((position (car (match-data))) | |||
| (matched-string (match-string 10))) | |||
| (set-text-properties 0 (length matched-string) nil matched-string) | |||
| (add-to-list 'tests (cons matched-string position) t))) | |||
| tests)))) | |||
| ;; Public functions | |||
| (defun alchemist-test-mode-jump-to-next-test () | |||
| "Jump to the next ExUnit test. If there are no tests after the current | |||
| position, jump to the first test in the buffer. Do nothing if there are no tests | |||
| in this buffer." | |||
| (interactive) | |||
| (alchemist-test-mode--jump-to-test 're-search-forward 'beginning-of-buffer)) | |||
| (defun alchemist-test-mode-jump-to-previous-test () | |||
| "Jump to the previous ExUnit test. If there are no tests before the current | |||
| position, jump to the last test in the buffer. Do nothing if there are no tests | |||
| in this buffer." | |||
| (interactive) | |||
| (alchemist-test-mode--jump-to-test 're-search-backward 'end-of-buffer)) | |||
| (defun alchemist-test-mode-list-tests () | |||
| "List ExUnit tests (calls to the test/2 macro) in the current buffer and jump | |||
| to the selected one." | |||
| (interactive) | |||
| (let* ((tests (alchemist-test-mode--tests-in-buffer)) | |||
| (selected (completing-read "Test: " tests)) | |||
| (position (cdr (assoc selected tests)))) | |||
| (goto-char position) | |||
| (back-to-indentation))) | |||
| (defun alchemist-test-mode--highlight-syntax () | |||
| (if alchemist-test-mode-highlight-tests | |||
| (font-lock-add-keywords nil | |||
| '(("^\s+\\(test\\)\s+" 1 | |||
| font-lock-variable-name-face t) | |||
| ("^\s+\\(assert[_a-z]*\\|refute[_a-z]*\\)\s+" 1 | |||
| font-lock-type-face t) | |||
| ("^\s+\\(assert[_a-z]*\\|refute[_a-z]*\\)\(" 1 | |||
| font-lock-type-face t))))) | |||
| ;;;###autoload | |||
| (define-minor-mode alchemist-test-mode | |||
| "Minor mode for Elixir ExUnit files. | |||
| The following commands are available: | |||
| \\{alchemist-test-mode-map}" | |||
| :lighter "" :keymap alchemist-test-mode-map | |||
| :group 'alchemist | |||
| (when alchemist-test-mode | |||
| (alchemist-test-mode--highlight-syntax))) | |||
| ;;;###autoload | |||
| (defun alchemist-test-enable-mode () | |||
| (if (alchemist-utils--is-test-file-p) | |||
| (alchemist-test-mode))) | |||
| ;;;###autoload | |||
| (dolist (hook '(alchemist-mode-hook)) | |||
| (add-hook hook 'alchemist-test-enable-mode)) | |||
| (provide 'alchemist-test-mode) | |||
| ;;; alchemist-test-mode.el ends here | |||
| @ -0,0 +1,224 @@ | |||
| ;;; alchemist.el --- Elixir tooling integration into Emacs | |||
| ;; Copyright © 2014-2015 Samuel Tonini | |||
| ;; | |||
| ;; Author: Samuel Tonini <tonini.samuel@gmail.com> | |||
| ;; URL: http://www.github.com/tonini/alchemist.el | |||
| ;; Version: 1.1.0 | |||
| ;; Package-Requires: ((emacs "24")) | |||
| ;; Keywords: languages, mix, elixir, elixirc, hex | |||
| ;; 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: | |||
| ;; Alchemist integrate Elixir's tooling into Emacs | |||
| ;;; Code: | |||
| (require 'easymenu) | |||
| (defgroup alchemist nil | |||
| "Elixir Tooling Integration Into Emacs." | |||
| :prefix "alchemist-" | |||
| :group 'applications | |||
| :link '(url-link :tag "Github" "https://github.com/tonini/alchemist.el") | |||
| :link '(emacs-commentary-link :tag "Commentary" "alchemist")) | |||
| (defcustom alchemist-key-command-prefix | |||
| (kbd "C-c a") | |||
| "The prefix for alchemist related key commands." | |||
| :type 'string | |||
| :group 'alchemist) | |||
| (require 'alchemist-utils) | |||
| (require 'alchemist-project) | |||
| (require 'alchemist-server) | |||
| (require 'alchemist-buffer) | |||
| (require 'alchemist-compile) | |||
| (require 'alchemist-execute) | |||
| (require 'alchemist-mix) | |||
| (require 'alchemist-hooks) | |||
| (require 'alchemist-help) | |||
| (require 'alchemist-complete) | |||
| (require 'alchemist-message) | |||
| (require 'alchemist-iex) | |||
| (require 'alchemist-eval) | |||
| (require 'alchemist-goto) | |||
| (require 'alchemist-test-mode) | |||
| (eval-after-load 'company | |||
| '(progn | |||
| (require 'alchemist-company))) | |||
| (defun alchemist-mode-hook () | |||
| "Hook which enables `alchemist-mode'" | |||
| (alchemist-mode 1)) | |||
| (defvar alchemist--version "1.1.0") | |||
| ;;;###autoload | |||
| (defun alchemist-version (&optional show-version) | |||
| "Display Alchemist's version." | |||
| (interactive) | |||
| (message "Alchemist %s" (replace-regexp-in-string "-cvs" "snapshot" alchemist--version))) | |||
| (define-prefix-command 'alchemist-mode-keymap) | |||
| ;;;###autoload | |||
| (define-minor-mode alchemist-mode | |||
| "Toggle alchemist mode. | |||
| Key bindings: | |||
| \\{alchemist-mode-map}" | |||
| nil | |||
| ;; The indicator for the mode line. | |||
| " alchemist" | |||
| :group 'alchemist | |||
| :global nil | |||
| :keymap `((,alchemist-key-command-prefix . alchemist-mode-keymap)) | |||
| (cond (alchemist-mode | |||
| (alchemist-buffer-initialize-modeline)) | |||
| (t | |||
| (alchemist-buffer-reset-modeline)))) | |||
| (let ((map alchemist-mode-keymap)) | |||
| (define-key map (kbd "x") 'alchemist-mix) | |||
| (define-key map (kbd "t") 'alchemist-mix-test) | |||
| (define-key map (kbd "m c") 'alchemist-mix-compile) | |||
| (define-key map (kbd "m t f") 'alchemist-mix-test-file) | |||
| (define-key map (kbd "m t b") 'alchemist-mix-test-this-buffer) | |||
| (define-key map (kbd "m t .") 'alchemist-mix-test-at-point) | |||
| (define-key map (kbd "c c") 'alchemist-compile) | |||
| (define-key map (kbd "c f") 'alchemist-compile-file) | |||
| (define-key map (kbd "c b") 'alchemist-compile-this-buffer) | |||
| (define-key map (kbd "e e") 'alchemist-execute) | |||
| (define-key map (kbd "e f") 'alchemist-execute-file) | |||
| (define-key map (kbd "e b") 'alchemist-execute-this-buffer) | |||
| (define-key map (kbd "h h") 'alchemist-help) | |||
| (define-key map (kbd "h i") 'alchemist-help-history) | |||
| (define-key map (kbd "h e") 'alchemist-help-search-at-point) | |||
| (define-key map (kbd "h m") 'alchemist-help-search-marked-region) | |||
| (define-key map (kbd "p f") 'alchemist-project-find-test) | |||
| (define-key map (kbd "p s") 'alchemist-project-toggle-file-and-tests) | |||
| (define-key map (kbd "p o") 'alchemist-project-toggle-file-and-tests-other-window) | |||
| (define-key map (kbd "i i") 'alchemist-iex-run) | |||
| (define-key map (kbd "i p") 'alchemist-iex-project-run) | |||
| (define-key map (kbd "i l") 'alchemist-iex-send-current-line) | |||
| (define-key map (kbd "i c") 'alchemist-iex-send-current-line-and-go) | |||
| (define-key map (kbd "i r") 'alchemist-iex-send-region) | |||
| (define-key map (kbd "i m") 'alchemist-iex-send-region-and-go) | |||
| (define-key map (kbd "i b") 'alchemist-iex-compile-this-buffer) | |||
| (define-key map (kbd "v l") 'alchemist-eval-current-line) | |||
| (define-key map (kbd "v k") 'alchemist-eval-print-current-line) | |||
| (define-key map (kbd "v j") 'alchemist-eval-quoted-current-line) | |||
| (define-key map (kbd "v h") 'alchemist-eval-print-quoted-current-line) | |||
| (define-key map (kbd "v o") 'alchemist-eval-region) | |||
| (define-key map (kbd "v i") 'alchemist-eval-print-region) | |||
| (define-key map (kbd "v u") 'alchemist-eval-quoted-region) | |||
| (define-key map (kbd "v y") 'alchemist-eval-print-quoted-region) | |||
| (define-key map (kbd "v q") 'alchemist-eval-buffer) | |||
| (define-key map (kbd "v w") 'alchemist-eval-print-buffer) | |||
| (define-key map (kbd "v e") 'alchemist-eval-quoted-buffer) | |||
| (define-key map (kbd "v r") 'alchemist-eval-print-quoted-buffer)) | |||
| (define-key alchemist-mode-map (kbd "M-.") 'alchemist-goto-definition-at-point) | |||
| (define-key alchemist-mode-map (kbd "M-,") 'alchemist-goto-jump-back) | |||
| (define-key alchemist-mode-map (kbd "C-c , .") 'alchemist-goto-list-symbol-definitions) | |||
| (easy-menu-define alchemist-mode-menu alchemist-mode-map | |||
| "Alchemist mode menu." | |||
| '("Alchemist" | |||
| ("Goto" | |||
| ["Jump to definiton at point" alchemist-goto-definition-at-point] | |||
| ["Jump back" alchemist-goto-jump-back]) | |||
| ("Evaluate" | |||
| ["Evaluate current line" alchemist-eval-current-line] | |||
| ["Evaluate current line and print" alchemist-eval-print-current-line] | |||
| ["Evaluate quoted current line" alchemist-eval-quoted-current-line] | |||
| ["Evaluate quoted current line and print" alchemist-eval-print-quoted-current-line] | |||
| "---" | |||
| ["Evaluate region" alchemist-eval-region] | |||
| ["Evaluate region and print" alchemist-eval-print-region] | |||
| ["Evaluate quoted region" alchemist-eval-quoted-region] | |||
| ["Evaluate quoted region and print" alchemist-eval-print-quoted-region] | |||
| "---" | |||
| ["Evaluate buffer" alchemist-eval-buffer] | |||
| ["Evaluate buffer and print" alchemist-eval-print-buffer] | |||
| ["Evaluate quoted buffer" alchemist-eval-quoted-buffer] | |||
| ["Evaluate quoted buffer and print" alchemist-eval-print-quoted-buffer]) | |||
| ("Compile" | |||
| ["Compile..." alchemist-compile] | |||
| ["Compile this buffer" alchemist-compile-this-buffer] | |||
| ["Compile file" alchemist-compile-file]) | |||
| ("Execute" | |||
| ["Execute..." alchemist-compile] | |||
| ["Execute this buffer" alchemist-execute-this-buffer] | |||
| ["Execute file" alchemist-execute-file]) | |||
| ("Mix" | |||
| ["Mix deps..." alchemist-mix-deps-with-prompt] | |||
| ["Mix compile..." alchemist-mix-compile] | |||
| ["Mix run..." alchemist-mix-run] | |||
| "---" | |||
| ["Mix test this buffer" alchemist-mix-test-this-buffer] | |||
| ["Mix test file..." alchemist-mix-test-file] | |||
| ["Mix test at point" alchemist-mix-test-at-point] | |||
| "---" | |||
| ["Mix..." alchemist-mix] | |||
| ["Mix new..." alchemist-mix-new] | |||
| ["Mix hex search..." alchemist-mix-hex-search] | |||
| "---" | |||
| ["Mix local..." alchemist-mix-local-with-prompt] | |||
| ["Mix local install..." alchemist-mix-local-install] | |||
| ["Mix local install (Path)..." alchemist-mix-local-install-with-path] | |||
| ["Mix local install (URL)..." alchemist-mix-local-install-with-url] | |||
| "---" | |||
| ["Display mix buffer" alchemist-mix-display-mix-buffer] | |||
| "---" | |||
| ["Mix help..." alchemist-mix-help]) | |||
| ("IEx" | |||
| ["IEx send current line" alchemist-iex-send-current-line] | |||
| ["IEx send current line and go" alchemist-iex-send-current-line-and-go] | |||
| "---" | |||
| ["IEx send last region" alchemist-iex-send-last-sexp] | |||
| ["IEx send region" alchemist-iex-send-region] | |||
| ["IEx send region and go" alchemist-iex-send-region-and-go] | |||
| "---" | |||
| ["IEx compile this buffer" alchemist-iex-compile-this-buffer] | |||
| ["IEx recompile this buffer" alchemist-iex-recompile-this-buffer] | |||
| "---" | |||
| ["IEx run" alchemist-iex-run]) | |||
| ("Project" | |||
| ["Project find all tests" alchemist-project-find-test] | |||
| ["Project toggle between file and test" alchemist-project-toggle-file-and-tests] | |||
| ["Project toggle between file and test in other window" alchemist-project-toggle-file-and-tests-other-window] | |||
| "---" | |||
| ["Project toggle compile when needed" alchemist-project-toggle-compile-when-needed | |||
| :style toggle :selected alchemist-project-compile-when-needed]) | |||
| ("Documentation" | |||
| ["Documentation search..." alchemist-help] | |||
| ["Documentation search history..." alchemist-help-history] | |||
| "---" | |||
| ["Documentation search at point..." alchemist-help-search-at-point] | |||
| ["Documentation search marked region..." alchemist-help-search-marked-region]) | |||
| )) | |||
| (add-hook 'elixir-mode-hook 'alchemist-mode-hook) | |||
| (provide 'alchemist) | |||
| ;;; alchemist.el ends here | |||
| @ -0,0 +1,96 @@ | |||
| defmodule Alchemist.Case do | |||
| alias Alchemist.Completer | |||
| alias Alchemist.Utils | |||
| alias Alchemist.Informant | |||
| defmodule Complete do | |||
| def process! do | |||
| :ets.insert(:alchemist, {"aliases", []}) | |||
| Completer.run('') | |||
| |> Enum.map &IO.puts('cmp:' ++ &1) | |||
| print_end_of_complete_signal | |||
| end | |||
| def process!(hint) do | |||
| :ets.insert(:alchemist, {"aliases", []}) | |||
| Completer.run(hint) | |||
| |> Enum.map &IO.puts('cmp:' ++ &1) | |||
| print_end_of_complete_signal | |||
| end | |||
| defp print_end_of_complete_signal do | |||
| IO.puts "END-OF-COMPLETE" | |||
| end | |||
| def process_with_context!(hint) do | |||
| [hint, modules, aliases] = String.split(hint, ";", parts: 3) | |||
| modules = Utils.clear_context_list(modules) | |||
| {modules, _} = Code.eval_string(modules) | |||
| {aliases, _} = Code.eval_string(aliases) | |||
| :ets.insert(:alchemist, {"aliases", aliases}) | |||
| Completer.run(hint) | |||
| |> Enum.map &IO.puts('cmp:' ++ &1) | |||
| Enum.each modules, fn(module) -> | |||
| Informant.get_functions(module, hint) | |||
| |> Enum.map &IO.puts('cmp:' ++ &1) | |||
| end | |||
| IO.puts "END-OF-COMPLETE-WITH-CONTEXT" | |||
| end | |||
| end | |||
| defmodule Modules do | |||
| def process! do | |||
| Informant.get_modules |> Enum.map &IO.puts/1 | |||
| IO.puts "END-OF-MODULES" | |||
| end | |||
| end | |||
| defmodule Doc do | |||
| def process!(exp) do | |||
| Code.eval_string("import IEx.Helpers \nApplication.put_env(:iex, :colors, [enabled: true])\nh(#{exp})", [], __ENV__) | |||
| IO.puts "END-OF-DOC" | |||
| end | |||
| end | |||
| defmodule Eval do | |||
| def process!(file) do | |||
| try do | |||
| File.read!("#{file}") | |||
| |> Code.eval_string | |||
| |> Tuple.to_list | |||
| |> List.first | |||
| |> IO.inspect | |||
| rescue | |||
| e -> IO.inspect e | |||
| end | |||
| IO.puts "END-OF-EVAL" | |||
| end | |||
| end | |||
| defmodule Quote do | |||
| def process!(file) do | |||
| try do | |||
| File.read!("#{file}") | |||
| |> Code.string_to_quoted | |||
| |> Tuple.to_list | |||
| |> List.last | |||
| |> IO.inspect | |||
| rescue | |||
| e -> IO.inspect e | |||
| end | |||
| IO.puts "END-OF-QUOTE" | |||
| end | |||
| end | |||
| defmodule Find do | |||
| def process!(exp) do | |||
| [module, function] = String.split(exp, ",", parts: 2) | |||
| module = String.to_char_list module | |||
| function = String.to_atom function | |||
| Code.eval_string("Alchemist.Source.find(#{module}, :#{function})", [], __ENV__) | |||
| IO.puts "END-OF-SOURCE" | |||
| end | |||
| end | |||
| end | |||
| @ -0,0 +1,340 @@ | |||
| defmodule Alchemist.Completer do | |||
| @moduledoc false | |||
| def run(exp) do | |||
| code = case is_bitstring(exp) do | |||
| true -> exp |> String.to_char_list | |||
| _ -> exp | |||
| end | |||
| {status, result, list } = expand(code |> Enum.reverse) | |||
| case { status, result, list } do | |||
| { :no, _, _ } -> '' | |||
| { :yes, [], _ } -> List.insert_at(list, 0, exp) | |||
| { :yes, _, _ } -> run(code ++ result) | |||
| end | |||
| end | |||
| def expand('') do | |||
| expand_import("") | |||
| end | |||
| def expand([h|t]=expr) do | |||
| cond do | |||
| h === ?. and t != []-> | |||
| expand_dot(reduce(t)) | |||
| h === ?: -> | |||
| expand_erlang_modules() | |||
| identifier?(h) -> | |||
| expand_expr(reduce(expr)) | |||
| (h == ?/) and t != [] and identifier?(hd(t)) -> | |||
| expand_expr(reduce(t)) | |||
| h in '([{' -> | |||
| expand('') | |||
| true -> | |||
| no() | |||
| end | |||
| end | |||
| defp identifier?(h) do | |||
| (h in ?a..?z) or (h in ?A..?Z) or (h in ?0..?9) or h in [?_, ??, ?!] | |||
| end | |||
| defp expand_dot(expr) do | |||
| case Code.string_to_quoted expr do | |||
| {:ok, atom} when is_atom(atom) -> | |||
| expand_call(atom, "") | |||
| {:ok, {:__aliases__, _, list}} -> | |||
| expand_elixir_modules(list, "") | |||
| _ -> | |||
| no() | |||
| end | |||
| end | |||
| defp expand_expr(expr) do | |||
| case Code.string_to_quoted expr do | |||
| {:ok, atom} when is_atom(atom) -> | |||
| expand_erlang_modules(Atom.to_string(atom)) | |||
| {:ok, {atom, _, nil}} when is_atom(atom) -> | |||
| expand_import(Atom.to_string(atom)) | |||
| {:ok, {:__aliases__, _, [root]}} -> | |||
| expand_elixir_modules([], Atom.to_string(root)) | |||
| {:ok, {:__aliases__, _, [h|_] = list}} when is_atom(h) -> | |||
| hint = Atom.to_string(List.last(list)) | |||
| list = Enum.take(list, length(list) - 1) | |||
| expand_elixir_modules(list, hint) | |||
| {:ok, {{:., _, [mod, fun]}, _, []}} when is_atom(fun) -> | |||
| expand_call(mod, Atom.to_string(fun)) | |||
| _ -> | |||
| no() | |||
| end | |||
| end | |||
| defp reduce(expr) do | |||
| Enum.reverse Enum.reduce ' ([{', expr, fn token, acc -> | |||
| hd(:string.tokens(acc, [token])) | |||
| end | |||
| end | |||
| defp yes(hint, entries) do | |||
| {:yes, String.to_char_list(hint), Enum.map(entries, &String.to_char_list/1)} | |||
| end | |||
| defp no do | |||
| {:no, '', []} | |||
| end | |||
| ## Formatting | |||
| defp format_expansion([], _) do | |||
| no() | |||
| end | |||
| defp format_expansion([uniq], hint) do | |||
| case to_hint(uniq, hint) do | |||
| "" -> yes("", to_uniq_entries(uniq)) | |||
| hint -> yes(hint, []) | |||
| end | |||
| end | |||
| defp format_expansion([first|_]=entries, hint) do | |||
| binary = Enum.map(entries, &(&1.name)) | |||
| length = byte_size(hint) | |||
| prefix = :binary.longest_common_prefix(binary) | |||
| if prefix in [0, length] do | |||
| yes("", Enum.flat_map(entries, &to_entries/1)) | |||
| else | |||
| yes(:binary.part(first.name, prefix, length-prefix), []) | |||
| end | |||
| end | |||
| ## Expand calls | |||
| # :atom.fun | |||
| defp expand_call(mod, hint) when is_atom(mod) do | |||
| expand_require(mod, hint) | |||
| end | |||
| # Elixir.fun | |||
| defp expand_call({:__aliases__, _, list}, hint) do | |||
| expand_alias(list) | |||
| |> normalize_module | |||
| |> expand_require(hint) | |||
| end | |||
| defp expand_call(_, _) do | |||
| no() | |||
| end | |||
| defp expand_require(mod, hint) do | |||
| format_expansion match_module_funs(mod, hint), hint | |||
| end | |||
| defp expand_import(hint) do | |||
| funs = match_module_funs(IEx.Helpers, hint) ++ | |||
| match_module_funs(Kernel, hint) ++ | |||
| match_module_funs(Kernel.SpecialForms, hint) | |||
| format_expansion funs, hint | |||
| end | |||
| ## Erlang modules | |||
| defp expand_erlang_modules(hint \\ "") do | |||
| format_expansion match_erlang_modules(hint), hint | |||
| end | |||
| defp match_erlang_modules(hint) do | |||
| for mod <- match_modules(hint, true) do | |||
| %{kind: :module, name: mod, type: :erlang} | |||
| end | |||
| end | |||
| ## Elixir modules | |||
| defp expand_elixir_modules([], hint) do | |||
| expand_elixir_modules(Elixir, hint, match_aliases(hint)) | |||
| end | |||
| defp expand_elixir_modules(list, hint) do | |||
| expand_alias(list) | |||
| |> normalize_module | |||
| |> expand_elixir_modules(hint, []) | |||
| end | |||
| defp expand_elixir_modules(mod, hint, aliases) do | |||
| aliases | |||
| |> Kernel.++(match_elixir_modules(mod, hint)) | |||
| |> Kernel.++(match_module_funs(mod, hint)) | |||
| |> format_expansion(hint) | |||
| end | |||
| defp expand_alias([name | rest] = list) do | |||
| module = Module.concat(Elixir, name) | |||
| Enum.find_value env_aliases(), list, fn {alias, mod} -> | |||
| if alias === module do | |||
| case Atom.to_string(mod) do | |||
| "Elixir." <> mod -> | |||
| Module.concat [mod|rest] | |||
| _ -> | |||
| mod | |||
| end | |||
| end | |||
| end | |||
| end | |||
| defp env_aliases() do | |||
| :ets.lookup(:alchemist, "aliases") | |||
| |> format_ets_aliases | |||
| end | |||
| defp format_ets_aliases([{"aliases", []}]) do | |||
| [] | |||
| end | |||
| defp format_ets_aliases(list) do | |||
| list | |||
| |> List.first | |||
| |> Tuple.to_list | |||
| |> List.last | |||
| end | |||
| defp match_aliases(hint) do | |||
| for {alias, _mod} <- env_aliases(), | |||
| [name] = Module.split(alias), | |||
| starts_with?(name, hint) do | |||
| %{kind: :module, type: :alias, name: name} | |||
| end | |||
| end | |||
| defp match_elixir_modules(module, hint) do | |||
| name = Atom.to_string(module) | |||
| depth = length(String.split(name, ".")) + 1 | |||
| base = name <> "." <> hint | |||
| for mod <- match_modules(base, module === Elixir), | |||
| parts = String.split(mod, "."), | |||
| depth <= length(parts) do | |||
| %{kind: :module, type: :elixir, name: Enum.at(parts, depth-1)} | |||
| end | |||
| |> Enum.uniq | |||
| end | |||
| ## Helpers | |||
| defp normalize_module(mod) do | |||
| if is_list(mod) do | |||
| Module.concat(mod) | |||
| else | |||
| mod | |||
| end | |||
| end | |||
| defp match_modules(hint, root) do | |||
| get_modules(root) | |||
| |> :lists.usort() | |||
| |> Enum.drop_while(& not starts_with?(&1, hint)) | |||
| |> Enum.take_while(& starts_with?(&1, hint)) | |||
| end | |||
| defp get_modules(true) do | |||
| ["Elixir.Elixir"] ++ get_modules(false) | |||
| end | |||
| defp get_modules(false) do | |||
| modules = Enum.map(:code.all_loaded(), &Atom.to_string(elem(&1, 0))) | |||
| case :code.get_mode() do | |||
| :interactive -> modules ++ get_modules_from_applications() | |||
| _otherwise -> modules | |||
| end | |||
| end | |||
| defp get_modules_from_applications do | |||
| for [app] <- loaded_applications(), | |||
| {:ok, modules} = :application.get_key(app, :modules), | |||
| module <- modules do | |||
| Atom.to_string(module) | |||
| end | |||
| end | |||
| defp loaded_applications do | |||
| # If we invoke :application.loaded_applications/0, | |||
| # it can error if we don't call safe_fixtable before. | |||
| # Since in both cases we are reaching over the | |||
| # application controller internals, we choose to match | |||
| # for performance. | |||
| :ets.match(:ac_tab, {{:loaded, :"$1"}, :_}) | |||
| end | |||
| defp match_module_funs(mod, hint) do | |||
| case ensure_loaded(mod) do | |||
| {:module, _} -> | |||
| falist = get_module_funs(mod) | |||
| list = Enum.reduce falist, [], fn {f, a}, acc -> | |||
| case :lists.keyfind(f, 1, acc) do | |||
| {f, aa} -> :lists.keyreplace(f, 1, acc, {f, [a|aa]}) | |||
| false -> [{f, [a]}|acc] | |||
| end | |||
| end | |||
| for {fun, arities} <- list, | |||
| name = Atom.to_string(fun), | |||
| starts_with?(name, hint) do | |||
| %{kind: :function, name: name, arities: arities} | |||
| end |> :lists.sort() | |||
| _otherwise -> [] | |||
| end | |||
| end | |||
| defp get_module_funs(mod) do | |||
| if function_exported?(mod, :__info__, 1) do | |||
| if docs = Code.get_docs(mod, :docs) do | |||
| for {tuple, _line, _kind, _sign, doc} <- docs, doc != false, do: tuple | |||
| else | |||
| mod.__info__(:macros) ++ (mod.__info__(:functions) -- [__info__: 1]) | |||
| end | |||
| else | |||
| mod.module_info(:exports) | |||
| end | |||
| end | |||
| defp ensure_loaded(Elixir), do: {:error, :nofile} | |||
| defp ensure_loaded(mod), | |||
| do: Code.ensure_compiled(mod) | |||
| defp starts_with?(_string, ""), do: true | |||
| defp starts_with?(string, hint), do: String.starts_with?(string, hint) | |||
| ## Ad-hoc conversions | |||
| defp to_entries(%{kind: :module, name: name}) do | |||
| [name] | |||
| end | |||
| defp to_entries(%{kind: :function, name: name, arities: arities}) do | |||
| for a <- :lists.sort(arities), do: "#{name}/#{a}" | |||
| end | |||
| defp to_uniq_entries(%{kind: :module}) do | |||
| [] | |||
| end | |||
| defp to_uniq_entries(%{kind: :function} = fun) do | |||
| to_entries(fun) | |||
| end | |||
| defp to_hint(%{kind: :module, name: name}, hint) do | |||
| format_hint(name, hint) <> "." | |||
| end | |||
| defp to_hint(%{kind: :function, name: name}, hint) do | |||
| format_hint(name, hint) | |||
| end | |||
| defp format_hint(name, hint) do | |||
| hint_size = byte_size(hint) | |||
| :binary.part(name, hint_size, byte_size(name) - hint_size) | |||
| end | |||
| end | |||
| @ -0,0 +1,55 @@ | |||
| defmodule Alchemist.Informant do | |||
| def get_functions(mod, hint) do | |||
| {mod, _} = Code.eval_string(mod) | |||
| falist = get_module_funs(mod) | |||
| list = Enum.reduce falist, [], fn({f, a}, acc) -> | |||
| case :lists.keyfind(f, 1, acc) do | |||
| {f, aa} -> :lists.keyreplace(f, 1, acc, {f, [a|aa]}) | |||
| false -> [{f, [a]}|acc] | |||
| end | |||
| end | |||
| case hint do | |||
| "" -> | |||
| for {fun, arities} <- list, | |||
| name = Atom.to_string(fun) do | |||
| "#{name}/#{List.first(arities)}" | |||
| end |> :lists.sort() | |||
| _otherwise -> | |||
| for {fun, arities} <- list, | |||
| name = Atom.to_string(fun), | |||
| String.starts_with?(name, hint) do | |||
| "#{name}/#{List.first(arities)}" | |||
| end |> :lists.sort() | |||
| end | |||
| end | |||
| defp get_module_funs(mod) do | |||
| case Code.ensure_loaded(mod) do | |||
| {:module, _} -> | |||
| mod.module_info(:functions) ++ mod.__info__(:macros) | |||
| _otherwise -> | |||
| [] | |||
| end | |||
| end | |||
| 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 | |||
| @ -0,0 +1,92 @@ | |||
| Code.require_file "utils.exs", __DIR__ | |||
| Code.require_file "completer.exs", __DIR__ | |||
| Code.require_file "informant.exs", __DIR__ | |||
| Code.require_file "source.exs", __DIR__ | |||
| Code.require_file "case.exs", __DIR__ | |||
| defmodule Alchemist.Server do | |||
| alias Alchemist.Case | |||
| def start([env]) do | |||
| # Preload Enum so we load basic Elixir/Erlang code | |||
| IEx.Autocomplete.expand('.munE') | |||
| :ets.new(:alchemist, [:named_table]) | |||
| loop(all_loaded(), env) | |||
| end | |||
| def loop(loaded, env) do | |||
| line = IO.gets("") |> String.rstrip() | |||
| paths = load_paths(env) | |||
| apps = load_apps(env) | |||
| read_input(line) | |||
| purge_modules(loaded) | |||
| purge_paths(paths) | |||
| purge_apps(apps) | |||
| :ets.delete_all_objects(:alchemist) | |||
| loop(loaded, env) | |||
| end | |||
| def read_input(line) do | |||
| case line |> String.split(" ", parts: 2) do | |||
| ["COMPLETE"] -> | |||
| Case.Complete.process! | |||
| ["COMPLETE", hint] -> | |||
| Case.Complete.process!(hint) | |||
| ["COMPLETE-WITH-CONTEXT", hint] -> | |||
| Case.Complete.process_with_context!(hint) | |||
| ["DOC", exp] -> | |||
| Case.Doc.process!(exp) | |||
| ["MODULES"] -> | |||
| Case.Modules.process! | |||
| ["EVAL", exp] -> | |||
| Case.Eval.process!(exp) | |||
| ["QUOTE", file] -> | |||
| Case.Quote.process!(file) | |||
| ["SOURCE", exp] -> | |||
| Case.Find.process!(exp) | |||
| _ -> | |||
| nil | |||
| end | |||
| end | |||
| defp all_loaded() do | |||
| for {m,_} <- :code.all_loaded, do: m | |||
| end | |||
| defp load_paths(env) do | |||
| for path <- Path.wildcard("_build/#{env}/lib/*/ebin") do | |||
| Code.prepend_path(path) | |||
| path | |||
| end | |||
| end | |||
| defp load_apps(env) do | |||
| for path <- Path.wildcard("_build/#{env}/lib/*/ebin/*.app") do | |||
| app = path |> Path.basename() |> Path.rootname() |> String.to_atom | |||
| Application.load(app) | |||
| app | |||
| end | |||
| end | |||
| defp purge_modules(loaded) do | |||
| for m <- (all_loaded() -- loaded) do | |||
| :code.delete(m) | |||
| :code.purge(m) | |||
| end | |||
| end | |||
| defp purge_paths(paths) do | |||
| for p <- paths, do: Code.delete_path(p) | |||
| end | |||
| defp purge_apps(apps) do | |||
| for a <- apps, do: Application.unload(a) | |||
| end | |||
| end | |||
| Alchemist.Server.start([System.argv]) | |||
| @ -0,0 +1,46 @@ | |||
| defmodule Alchemist.Source do | |||
| def find(nil, function) do | |||
| cond do | |||
| List.keymember?(get_module_funs(Kernel), function, 0) -> | |||
| IO.puts source(Kernel) | |||
| List.keymember?(get_module_funs(Kernel.SpecialForms), function, 0) -> | |||
| IO.puts source(Kernel.SpecialForms) | |||
| true -> | |||
| IO.puts "" | |||
| end | |||
| end | |||
| def find(module, function) do | |||
| cond do | |||
| Code.ensure_loaded?(module) -> | |||
| IO.puts source(module) | |||
| List.keymember?(Kernel.module_info[:exports], function, 0) -> | |||
| IO.puts source(Kernel) | |||
| List.keymember?(Kernel.SpecialForms.module_info[:exports], function, 0) -> | |||
| IO.puts source(Kernel.SpecialForms) | |||
| true -> | |||
| IO.puts "" | |||
| end | |||
| end | |||
| defp source(module) do | |||
| source = module.module_info(:compile)[:source] | |||
| case source do | |||
| nil -> nil | |||
| source -> "source-file-path:" <> List.to_string(source) | |||
| end | |||
| end | |||
| defp get_module_funs(mod) do | |||
| if function_exported?(mod, :__info__, 1) do | |||
| if docs = Code.get_docs(mod, :docs) do | |||
| for {tuple, _line, _kind, _sign, doc} <- docs, doc != false, do: tuple | |||
| else | |||
| (mod.__info__(:functions) -- [__info__: 1]) ++ mod.__info__(:macros) | |||
| end | |||
| else | |||
| mod.module_info(:exports) | |||
| end | |||
| end | |||
| end | |||
| @ -0,0 +1,6 @@ | |||
| defmodule Alchemist.Utils do | |||
| def clear_context_list(modules) do | |||
| cleared = Regex.replace ~r/\.\]/, modules, "]" | |||
| Regex.replace ~r/\.\,/, cleared, "," | |||
| end | |||
| end | |||
| @ -1,4 +0,0 @@ | |||
| (define-package "async" "20150412.2207" "Asynchronous processing in Emacs" 'nil) | |||
| ;; Local Variables: | |||
| ;; no-byte-compile: t | |||
| ;; End: | |||
| @ -0,0 +1,4 @@ | |||
| (define-package "async" "20150529.529" "Asynchronous processing in Emacs" 'nil) | |||
| ;; Local Variables: | |||
| ;; no-byte-compile: t | |||
| ;; End: | |||
| @ -1 +0,0 @@ | |||
| (define-package "dash" "20150503.1343" "A modern list library for Emacs" 'nil :keywords '("lists")) | |||
| @ -0,0 +1 @@ | |||
| (define-package "dash" "20150611.922" "A modern list library for Emacs" 'nil :keywords '("lists")) | |||