| @ -0,0 +1,11 @@ | |||
| ;; This file is automatically generated by the multiple-cursors extension. | |||
| ;; It keeps track of your preferences for running commands with multiple cursors. | |||
| (setq mc/cmds-to-run-for-all | |||
| '( | |||
| )) | |||
| (setq mc/cmds-to-run-once | |||
| '( | |||
| end-of-buffer | |||
| )) | |||
| @ -1,81 +0,0 @@ | |||
| ;;; alchemist-autoloads.el --- automatically extracted autoloads | |||
| ;; | |||
| ;;; Code: | |||
| (add-to-list 'load-path (or (file-name-directory #$) (car load-path))) | |||
| ;;;### (autoloads nil "alchemist" "alchemist.el" (21898 47984 0 0)) | |||
| ;;; Generated autoloads from alchemist.el | |||
| (autoload 'alchemist-version "alchemist" "\ | |||
| Display Alchemist's version. | |||
| \(fn &optional SHOW-VERSION)" t nil) | |||
| (autoload 'alchemist-mode "alchemist" "\ | |||
| Toggle alchemist mode. | |||
| Key bindings: | |||
| \\{alchemist-mode-map} | |||
| \(fn &optional ARG)" t nil) | |||
| ;;;*** | |||
| ;;;### (autoloads nil "alchemist-iex" "alchemist-iex.el" (21898 47984 | |||
| ;;;;;; 0 0)) | |||
| ;;; Generated autoloads from alchemist-iex.el | |||
| (defalias 'run-elixir 'alchemist-iex-run) | |||
| (autoload 'alchemist-iex-run "alchemist-iex" "\ | |||
| Start an IEx process. | |||
| Show the IEx buffer if an IEx process is already run. | |||
| \(fn &optional ARG)" t nil) | |||
| (autoload 'alchemist-iex-project-run "alchemist-iex" "\ | |||
| Start an IEx process with mix 'iex -S mix' in the | |||
| context of an Elixir project. | |||
| Show the IEx buffer if an IEx process is already run. | |||
| \(fn)" t nil) | |||
| ;;;*** | |||
| ;;;### (autoloads nil "alchemist-test-mode" "alchemist-test-mode.el" | |||
| ;;;;;; (21898 47984 0 0)) | |||
| ;;; Generated autoloads from alchemist-test-mode.el | |||
| (autoload 'alchemist-test-mode "alchemist-test-mode" "\ | |||
| Minor mode for Elixir ExUnit files. | |||
| The following commands are available: | |||
| \\{alchemist-test-mode-map} | |||
| \(fn &optional ARG)" t nil) | |||
| (autoload 'alchemist-test-enable-mode "alchemist-test-mode" "\ | |||
| \(fn)" nil nil) | |||
| (dolist (hook '(alchemist-mode-hook)) (add-hook hook 'alchemist-test-enable-mode)) | |||
| ;;;*** | |||
| ;;;### (autoloads nil nil ("alchemist-buffer.el" "alchemist-company.el" | |||
| ;;;;;; "alchemist-compile.el" "alchemist-complete.el" "alchemist-eval.el" | |||
| ;;;;;; "alchemist-execute.el" "alchemist-goto.el" "alchemist-help.el" | |||
| ;;;;;; "alchemist-hooks.el" "alchemist-message.el" "alchemist-mix.el" | |||
| ;;;;;; "alchemist-pkg.el" "alchemist-project.el" "alchemist-server.el" | |||
| ;;;;;; "alchemist-utils.el") (21898 47984 781999 0)) | |||
| ;;;*** | |||
| ;; Local Variables: | |||
| ;; version-control: never | |||
| ;; no-byte-compile: t | |||
| ;; no-update-autoloads: t | |||
| ;; End: | |||
| ;;; alchemist-autoloads.el ends here | |||
| @ -1,152 +0,0 @@ | |||
| ;;; alchemist-buffer.el --- Custom compilation mode for Alchemist | |||
| ;; 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: | |||
| ;; Custom compilation mode for Alchemist | |||
| ;;; Code: | |||
| (require 'compile) | |||
| (require 'ansi-color) | |||
| ;; Variables | |||
| (defgroup alchemist-buffer nil | |||
| "Custom compilation mode for Alchemist." | |||
| :prefix "alchemist-buffer-" | |||
| :group 'alchemist) | |||
| (defcustom alchemist-buffer-status-modeline t | |||
| "if t, the face of local `mode-name' variable will change with compilation status. | |||
| For example, when `alchemist-mix-test' failes, the `mode-name' will be | |||
| formated with the `alchemist-buffer--failed-face' face, to symbolize failing tests." | |||
| :type 'boolean | |||
| :group 'alchemist-buffer) | |||
| (defvar alchemist-buffer--mode-name-face 'mode-line) | |||
| (defvar alchemist-buffer--buffer-name nil | |||
| "Used to store compilation name so recompilation works as expected.") | |||
| (make-variable-buffer-local 'alchemist-buffer--buffer-name) | |||
| (defvar alchemist-buffer--error-link-options | |||
| '(elixir "\\([-A-Za-z0-9./_]+\\):\\([0-9]+\\)\\(?: warning\\)?" 1 2 nil (3) 1) | |||
| "File link matcher for `compilation-error-regexp-alist-alist' (matches path/to/file:line).") | |||
| ;; Faces | |||
| (defface alchemist-buffer--success-face | |||
| '((t (:inherit font-lock-variable-name-face :bold t :background "darkgreen" :foreground "#e0ff00"))) | |||
| "Face for successful compilation run." | |||
| :group 'alchemist-buffer) | |||
| (defface alchemist-buffer--failed-face | |||
| '((t (:inherit font-lock-variable-name-face :bold t :background "red" :foreground "white"))) | |||
| "Face for failed compilation run." | |||
| :group 'alchemist-buffer) | |||
| (defface alchemist-buffer--running-face | |||
| '((t (:inherit font-lock-variable-name-face :bold nil :background "gray" :foreground "black"))) | |||
| "Face for running compilation." | |||
| :group 'alchemist-buffer) | |||
| (defun alchemist-buffer--kill-any-orphan-proc () | |||
| "Ensure any dangling buffer process is killed." | |||
| (let ((orphan-proc (get-buffer-process (buffer-name)))) | |||
| (when orphan-proc | |||
| (kill-process orphan-proc)))) | |||
| ;; Private functions | |||
| (defvar alchemist-buffer--save-buffers-predicate | |||
| (lambda () | |||
| (not (string= (substring (buffer-name) 0 1) "*")))) | |||
| (defun alchemist-buffer-init-test-report (buffer status) | |||
| (when (string= "*alchemist-test-report*" (buffer-name buffer)) | |||
| (alchemist-test-mode))) | |||
| (defun alchemist-buffer--remove-dispensable-output () | |||
| (delete-matching-lines "\\(-*- mode:\\|Compiled \\|elixir-compilation;\\|Elixir started\\|^$\\)" (point-min) (point-max)) | |||
| (remove-hook 'compilation-filter-hook 'alchemist-buffer--remove-dispensable-output t)) | |||
| (defun alchemist-buffer--remove-dispensable-output-after-finish (buffer msg) | |||
| (delete-matching-lines "\\(Excluding tags\\|Including tags\\|Elixir exited\\|Elixir finished\\)" (point-min) (point-max))) | |||
| (defun alchemist-buffer--handle-compilation () | |||
| (ansi-color-apply-on-region (point-min) (point-max))) | |||
| (defun alchemist-buffer--set-modeline-color (buffer status) | |||
| (setq alchemist-buffer--mode-name-face | |||
| (if (string-prefix-p "finished" status) | |||
| 'alchemist-buffer--success-face | |||
| 'alchemist-buffer--failed-face)) | |||
| (remove-hook 'compilation-finish-functions 'alchemist-buffer--set-modeline-color)) | |||
| ;; Public functions | |||
| (defun alchemist-buffer-initialize-modeline () | |||
| "Initialize the mode-line face." | |||
| (setq mode-name | |||
| '(:eval (propertize "Elixir" 'face alchemist-buffer--mode-name-face)))) | |||
| (defun alchemist-buffer-reset-modeline () | |||
| "Reset the current mode-line face to default." | |||
| (setq mode-name "Elixir")) | |||
| (define-compilation-mode alchemist-buffer-mode "Elixir" | |||
| "Elixir compilation mode." | |||
| (progn | |||
| (font-lock-add-keywords nil | |||
| '(("^Finished in .*$" . font-lock-string-face))) | |||
| ;; Set any bound buffer name buffer-locally | |||
| (setq alchemist-buffer--buffer-name alchemist-buffer--buffer-name) | |||
| (set (make-local-variable 'kill-buffer-hook) | |||
| 'alchemist-buffer--kill-any-orphan-proc))) | |||
| (defun alchemist-buffer-run (cmdlist buffer-name) | |||
| "Run CMDLIST in `alchemist-buffer-mode'. | |||
| Returns the compilation buffer. | |||
| Argument BUFFER-NAME for the compilation." | |||
| (save-some-buffers (not compilation-ask-about-save) alchemist-buffer--save-buffers-predicate) | |||
| (let* ((alchemist-buffer--buffer-name buffer-name) | |||
| (compilation-filter-start (point-min))) | |||
| (with-current-buffer | |||
| (compilation-start (mapconcat 'concat cmdlist " ") | |||
| 'alchemist-buffer-mode | |||
| (lambda (b) alchemist-buffer--buffer-name)) | |||
| (set (make-local-variable 'compilation-error-regexp-alist-alist) | |||
| (cons alchemist-buffer--error-link-options compilation-error-regexp-alist-alist)) | |||
| (set (make-local-variable 'compilation-error-regexp-alist) | |||
| (cons 'elixir compilation-error-regexp-alist)) | |||
| (add-hook 'compilation-filter-hook 'alchemist-buffer--handle-compilation nil t) | |||
| (add-hook 'compilation-filter-hook 'alchemist-buffer--remove-dispensable-output nil t) | |||
| (add-to-list 'compilation-finish-functions 'alchemist-buffer-init-test-report) | |||
| (add-to-list 'compilation-finish-functions 'alchemist-buffer--remove-dispensable-output-after-finish) | |||
| (when alchemist-buffer-status-modeline | |||
| (add-hook 'compilation-finish-functions 'alchemist-buffer--set-modeline-color nil t))))) | |||
| (provide 'alchemist-buffer) | |||
| ;;; alchemist-buffer.el ends here | |||
| @ -1,83 +0,0 @@ | |||
| ;;; alchemist-company.el --- Elixir company-mode backend -*- 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: | |||
| ;; Elixir company-mode backend. | |||
| ;;; Code: | |||
| (require 'company) | |||
| (defgroup alchemist-company nil | |||
| "Elixir company-mode backend." | |||
| :prefix "alchemist-company-" | |||
| :group 'alchemist) | |||
| ;; Variables | |||
| (defcustom alchemist-company-show-annotation t | |||
| "Show an annotation inline with the candidate." | |||
| :type 'boolean | |||
| :group 'alchemist-company) | |||
| (defun alchemist-company--show-documentation (selected) | |||
| (interactive) | |||
| (company--electric-do | |||
| (let* ((candidate (format "%s%s" selected (alchemist-company--annotation selected)))) | |||
| (alchemist-help--execute-without-complete candidate)))) | |||
| (put 'alchemist-company--show-documentation 'company-keep t) | |||
| (defun alchemist-company--open-definition (selected) | |||
| (interactive) | |||
| (company--electric-do | |||
| (alchemist-goto--open-definition selected))) | |||
| (put 'alchemist-company--open-definition 'company-keep t) | |||
| (defun alchemist-company--annotation (candidate) | |||
| (get-text-property 0 'meta candidate)) | |||
| (defun alchemist-company (command &optional arg &rest ignored) | |||
| "`company-mode' completion back-end for Elixir." | |||
| (interactive (list 'interactive)) | |||
| (when alchemist-company-show-annotation | |||
| (set 'company-tooltip-align-annotations t)) | |||
| (case command | |||
| (interactive (company-begin-backend 'alchemist-company)) | |||
| (init (when (or (eq major-mode 'elixir-mode) | |||
| (string= mode-name "Alchemist-IEx")))) | |||
| (prefix (and (or (eq major-mode 'elixir-mode) | |||
| (string= mode-name "Alchemist-IEx")) | |||
| (alchemist-help--exp-at-point))) | |||
| (doc-buffer (alchemist-company--show-documentation arg)) | |||
| (location (alchemist-company--open-definition arg)) | |||
| (candidates (cons :async | |||
| (lambda (cb) | |||
| (setq alchemist-server-company-callback cb) | |||
| (alchemist-server-complete-candidates arg)))) | |||
| (annotation (when alchemist-company-show-annotation | |||
| (alchemist-company--annotation arg))))) | |||
| (add-to-list 'company-backends 'alchemist-company) | |||
| (provide 'alchemist-company) | |||
| ;;; alchemist-company.el ends here | |||
| @ -1,73 +0,0 @@ | |||
| ;;; alchemist-compile.el --- Elixir compilation functionality | |||
| ;; 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: | |||
| ;; Elixir compilation functionality. | |||
| ;;; Code: | |||
| (defgroup alchemist-compile nil | |||
| "Elixir compilation functionality." | |||
| :prefix "alchemist-compile-" | |||
| :group 'alchemist) | |||
| ;; Variables | |||
| (defcustom alchemist-compile-command "elixirc" | |||
| "The shell command for elixirc." | |||
| :type 'string | |||
| :group 'alchemist-compile) | |||
| (defvar alchemist-compile-buffer-name "*elixirc*" | |||
| "Name of the elixir output buffer.") | |||
| ;; Private functions | |||
| (defun alchemist-compile--file (filename) | |||
| (cond ((not (file-exists-p filename)) (error "The given file doesn't exist")) | |||
| ((string-match "\.exs$" filename) (error "The given file is an Elixir Script")) | |||
| (t (alchemist-compile (list alchemist-compile-command (expand-file-name filename)))))) | |||
| (defun alchemist-compile--read-command (command) | |||
| (read-shell-command "elixirc command: " (concat command " "))) | |||
| ;; Public functions | |||
| (defun alchemist-compile-this-buffer () | |||
| "Compile the current buffer with elixirc." | |||
| (interactive) | |||
| (alchemist-compile--file buffer-file-name)) | |||
| (defun alchemist-compile-file (filename) | |||
| "Compile the given FILENAME." | |||
| (interactive "Felixirc: ") | |||
| (alchemist-compile--file (expand-file-name filename))) | |||
| (defun alchemist-compile (cmdlist) | |||
| "Compile CMDLIST with elixirc." | |||
| (interactive (list (alchemist-compile--read-command alchemist-compile-command))) | |||
| (alchemist-buffer-run (alchemist-utils--build-runner-cmdlist cmdlist) | |||
| alchemist-compile-buffer-name)) | |||
| (provide 'alchemist-compile) | |||
| ;;; alchemist-compile.el ends here | |||
| @ -1,135 +0,0 @@ | |||
| ;;; alchemist-complete.el --- Complete functionality for Elixir source code -*- 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: | |||
| ;; Complete functionality for Elixir and Erlang source code. | |||
| ;;; Code: | |||
| (defgroup alchemist-complete nil | |||
| "Complete functionality for Elixir source code." | |||
| :prefix "alchemist-complete-" | |||
| :group 'alchemist) | |||
| (defun alchemist-complete--concat-prefix-with-functions (prefix functions &optional add-prefix) | |||
| (let* ((prefix (mapconcat 'concat (butlast (split-string prefix "\\.") 1) ".")) | |||
| (candidates (mapcar (lambda (c) (concat prefix "." c)) (cdr functions)))) | |||
| (if add-prefix | |||
| (push prefix candidates) | |||
| candidates))) | |||
| (defun alchemist-complete--add-prefix-to-function (prefix function) | |||
| (let* ((prefix (mapconcat 'concat (butlast (split-string prefix "\\.") 1) ".")) | |||
| (candidate (concat prefix "." function))) | |||
| candidate)) | |||
| (defun alchemist-complete--build-candidates (a-list) | |||
| (let* ((search-term (car a-list)) | |||
| (candidates (if (string-match-p "^.+\/" search-term) | |||
| a-list | |||
| (cdr a-list))) | |||
| (candidates (mapcar (lambda (f) | |||
| (let* ((candidate f) | |||
| (meta (if (string-match-p "^.+/" f) | |||
| (replace-regexp-in-string "^.+/" "/" f) | |||
| ""))) | |||
| (cond | |||
| ((and (string-match-p "^:" search-term) | |||
| (not (string-match-p "\\.$" search-term))) | |||
| (propertize (concat ":" candidate))) | |||
| ((string-match-p "\\." search-term) | |||
| (propertize (alchemist-complete--add-prefix-to-function search-term | |||
| (replace-regexp-in-string "/[0-9]$" "" candidate)) 'meta meta)) | |||
| (t (propertize (replace-regexp-in-string "/[0-9]$" "" candidate) 'meta meta))))) | |||
| candidates))) | |||
| candidates)) | |||
| (defun alchemist-complete--build-help-candidates (a-list) | |||
| (let* ((search-term (car a-list)) | |||
| (candidates (cond ((> (alchemist-utils--count-char-in-str "\\." search-term) 1) | |||
| (let ((search (if (string-match-p "\\.[a-z0-9_\?!]+$" search-term) | |||
| (list (replace-regexp-in-string "\\.[a-z0-9_\?!]+$" "" search-term)) | |||
| (list (replace-regexp-in-string "\\.$" "" search-term)))) | |||
| (candidates (mapcar (lambda (c) | |||
| (if (string-match-p "\\.[a-z0-9_\?!]+$" search-term) | |||
| (concat (replace-regexp-in-string "\\.[a-z0-9_\?!]+$" "." search-term) c) | |||
| (concat search-term c))) | |||
| (cdr a-list)))) | |||
| (append search candidates))) | |||
| ((string-match-p "\\.$" search-term) | |||
| (alchemist-complete--concat-prefix-with-functions search-term a-list t)) | |||
| ((string-match-p "\\.[a-z0-9_\?!]+$" search-term) | |||
| (alchemist-complete--concat-prefix-with-functions search-term a-list)) | |||
| (t | |||
| a-list)))) | |||
| (delete-dups candidates))) | |||
| (defun alchemist-complete--output-to-list (output) | |||
| (let* ((output (replace-regexp-in-string "^cmp:" "" output)) | |||
| (output (split-string output)) | |||
| (output (delete nil output))) | |||
| output) | |||
| ) | |||
| (defun alchemist-complete--clear-buffer (buffer) | |||
| "Clears the BUFFER from not used lines." | |||
| (with-current-buffer buffer | |||
| (delete-non-matching-lines "^cmp:" (point-min) (point-max)))) | |||
| (defun alchemist-complete--completing-prompt (initial completing-collection) | |||
| (let* ((completing-collection (alchemist-complete--build-help-candidates completing-collection))) | |||
| (cond ((equal (length completing-collection) 1) | |||
| (car completing-collection)) | |||
| (completing-collection | |||
| (completing-read | |||
| "Elixir help: " | |||
| completing-collection | |||
| nil | |||
| nil | |||
| (replace-regexp-in-string "\\.$" "" initial))) | |||
| (t initial)))) | |||
| (defun alchemsit-complete--dabbrev-code-candidates () | |||
| "This function uses a piece of functionality of company-dabbrev-code backend. | |||
| Please have a look at the company-dabbrev-code function for more | |||
| detailed information." | |||
| (let ((case-fold-search company-dabbrev-code-ignore-case) | |||
| (candidates (company-dabbrev--search | |||
| (company-dabbrev-code--make-regexp alchemist-server--last-completion-exp) | |||
| company-dabbrev-code-time-limit | |||
| (pcase company-dabbrev-code-other-buffers | |||
| (`t (list major-mode)) | |||
| (`code company-dabbrev-code-modes) | |||
| (`all `all)) | |||
| t))) | |||
| (delete-dups candidates))) | |||
| (defun alchemist-complete--serve-candidates-to-company (candidates) | |||
| (let ((candidates (if candidates | |||
| candidates | |||
| (alchemsit-complete--dabbrev-code-candidates)))) | |||
| (funcall alchemist-server-company-callback candidates))) | |||
| (provide 'alchemist-complete) | |||
| ;;; alchemist-complete.el ends here | |||
| @ -1,185 +0,0 @@ | |||
| ;;; alchemist-eval.el --- Elixir code inline evaluation functionality | |||
| ;; 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: | |||
| ;; Elixir code inline evaluation functionality | |||
| ;;; Code: | |||
| (defgroup alchemist-eval nil | |||
| "Elixir code inline evaluation functionality." | |||
| :prefix "alchemist-eval-" | |||
| :group 'alchemist) | |||
| ;; Private functions | |||
| (defun alchemist-eval--insert (string) | |||
| (let ((lines (split-string string "\n"))) | |||
| (if (> (length lines) 1) | |||
| (progn | |||
| (save-excursion | |||
| (end-of-line) | |||
| (mapc (lambda (s) | |||
| (newline) | |||
| (insert (format "# => %s" s)) | |||
| (indent-according-to-mode)) | |||
| lines))) | |||
| (save-excursion | |||
| (end-of-line) | |||
| (insert (format " # => %s" string)))))) | |||
| (defun alchemist-eval--evaluate-code (string) | |||
| (let ((tmp-file ".alchemist-eval.exs") | |||
| (old-directory default-directory)) | |||
| (when (alchemist-project-p) | |||
| (alchemist-project--establish-root-directory)) | |||
| (with-temp-file tmp-file | |||
| (insert string)) | |||
| (let ((output (shell-command-to-string | |||
| (alchemist-eval--build-code-evaluation-command tmp-file)))) | |||
| (delete-file tmp-file) | |||
| (cd old-directory) | |||
| (alchemist-utils--remove-newline-at-end output)))) | |||
| (defun alchemist-eval--evaluate-code-as-quoted (string) | |||
| (let ((tmp-file ".alchemist-eval.exs") | |||
| (old-directory default-directory)) | |||
| (when (alchemist-project-p) | |||
| (alchemist-project--establish-root-directory)) | |||
| (with-temp-file tmp-file | |||
| (insert string)) | |||
| (let ((output (shell-command-to-string | |||
| (alchemist-eval--build-code-evaluation-as-quoted-command tmp-file)))) | |||
| (delete-file tmp-file) | |||
| (cd old-directory) | |||
| (alchemist-utils--remove-newline-at-end output)))) | |||
| (defun alchemist-eval--expression (expression) | |||
| (let ((file (make-temp-file "alchemist-eval" nil ".exs"))) | |||
| (with-temp-file file | |||
| (insert expression)) | |||
| (alchemist-server-eval file))) | |||
| (defun alchemist-eval--expression-and-print (expression) | |||
| (let ((file (make-temp-file "alchemist-eval" nil ".exs"))) | |||
| (with-temp-file file | |||
| (insert expression)) | |||
| (alchemist-server-eval-and-insert file))) | |||
| (defun alchemist-eval--quote-expression (expression) | |||
| (let ((file (make-temp-file "alchemist-eval" nil ".exs"))) | |||
| (with-temp-file file | |||
| (insert expression)) | |||
| (alchemist-server-eval-quote file))) | |||
| (defun alchemist-eval--quote-expression-and-print (expression) | |||
| (let ((file (make-temp-file "alchemist-eval" nil ".exs"))) | |||
| (with-temp-file file | |||
| (insert expression)) | |||
| (alchemist-server-eval-quote-and-insert file))) | |||
| ;; Public functions | |||
| (defun alchemist-eval-current-line () | |||
| "Evaluate the Elixir code on the current line." | |||
| (interactive) | |||
| (alchemist-eval--expression (thing-at-point 'line))) | |||
| (defun alchemist-eval-print-current-line () | |||
| "Evaluate the Elixir code on the current line and insert the result." | |||
| (interactive) | |||
| (alchemist-eval--expression-and-print (thing-at-point 'line))) | |||
| (defun alchemist-eval-region (beg end) | |||
| "Evaluate the Elixir code on marked region." | |||
| (interactive (list (point) (mark))) | |||
| (unless (and beg end) | |||
| (error "The mark is not set now, so there is no region")) | |||
| (let ((string (buffer-substring-no-properties beg end))) | |||
| (alchemist-eval--expression string))) | |||
| (defun alchemist-eval-print-region (beg end) | |||
| "Evaluate the Elixir code on marked region and insert the result." | |||
| (interactive (list (point) (mark))) | |||
| (unless (and beg end) | |||
| (error "The mark is not set now, so there is no region")) | |||
| (let ((string (buffer-substring-no-properties beg end))) | |||
| (when (> end beg) | |||
| (exchange-point-and-mark)) | |||
| (alchemist-eval--expression-and-print string))) | |||
| (defun alchemist-eval-buffer () | |||
| "Evaluate the Elixir code in the current buffer." | |||
| (interactive) | |||
| (let ((string (buffer-substring-no-properties (point-min) (point-max)))) | |||
| (alchemist-eval--expression string))) | |||
| (defun alchemist-eval-print-buffer () | |||
| "Evaluate the Elixir code in the current buffer and insert the result." | |||
| (interactive) | |||
| (let ((string (buffer-substring-no-properties (point-min) (point-max)))) | |||
| (end-of-buffer) | |||
| (alchemist-eval--expression-and-print string))) | |||
| (defun alchemist-eval-quoted-current-line () | |||
| "Get the Elixir code representation of the expression on the current line." | |||
| (interactive) | |||
| (alchemist-eval--quote-expression (thing-at-point 'line))) | |||
| (defun alchemist-eval-print-quoted-current-line () | |||
| "Get the Elixir code representation of the expression on the current line and insert the result." | |||
| (interactive) | |||
| (alchemist-eval--quote-expression-and-print (thing-at-point 'line))) | |||
| (defun alchemist-eval-quoted-region (beg end) | |||
| "Get the Elixir code representation of the expression on marked region." | |||
| (interactive (list (point) (mark))) | |||
| (unless (and beg end) | |||
| (error "The mark is not set now, so there is no region")) | |||
| (let ((string (buffer-substring-no-properties beg end))) | |||
| (alchemist-eval--quote-expression string))) | |||
| (defun alchemist-eval-print-quoted-region (beg end) | |||
| "Get the Elixir code representation of the expression on marked region and insert the result." | |||
| (interactive (list (point) (mark))) | |||
| (unless (and beg end) | |||
| (error "The mark is not set now, so there is no region")) | |||
| (let ((string (buffer-substring-no-properties beg end))) | |||
| (when (> end beg) | |||
| (exchange-point-and-mark)) | |||
| (alchemist-eval--quote-expression-and-print string))) | |||
| (defun alchemist-eval-quoted-buffer () | |||
| "Get the Elixir code representation of the expression in the current buffer." | |||
| (interactive) | |||
| (let ((string (buffer-substring-no-properties (point-min) (point-max)))) | |||
| (alchemist-eval--quote-expression string))) | |||
| (defun alchemist-eval-print-quoted-buffer () | |||
| "Get the Elixir code representation of the expression in the current buffer and insert result." | |||
| (interactive) | |||
| (let ((string (buffer-substring-no-properties (point-min) (point-max)))) | |||
| (alchemist-eval--quote-expression-and-print string))) | |||
| (provide 'alchemist-eval) | |||
| ;;; alchemist-eval.el ends here | |||
| @ -1,73 +0,0 @@ | |||
| ;;; alchemist-execute.el --- Elixir's script execution integration | |||
| ;; 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: | |||
| ;; Elixir's script execution integration | |||
| ;;; Code: | |||
| (defgroup alchemist-execute nil | |||
| "Elixir's script execution integration." | |||
| :prefix "alchemist-execute-" | |||
| :group 'alchemist) | |||
| ;; Variables | |||
| (defcustom alchemist-execute-command "elixir" | |||
| "The shell command for elixir." | |||
| :type 'string | |||
| :group 'alchemist-execute) | |||
| (defvar alchemist-execute-buffer-name "*elixir*" | |||
| "Name of the elixir output buffer.") | |||
| ;; Private functions | |||
| (defun alchemist-execute--file (filename) | |||
| (when (not (file-exists-p filename)) | |||
| (error "The given file doesn't exists")) | |||
| (alchemist-execute (list alchemist-execute-command (expand-file-name filename)))) | |||
| (defun alchemist-execute--read-command (command) | |||
| (read-shell-command "elixir command: " (concat command " "))) | |||
| ;; Public functions | |||
| (defun alchemist-execute-this-buffer () | |||
| "Run the current buffer through elixir." | |||
| (interactive) | |||
| (alchemist-execute--file buffer-file-name)) | |||
| (defun alchemist-execute-file (filename) | |||
| "Run elixir with the given FILENAME." | |||
| (interactive "Felixir: ") | |||
| (alchemist-execute--file (expand-file-name filename))) | |||
| (defun alchemist-execute (cmdlist) | |||
| "Run a elixir with CMDLIST." | |||
| (interactive (list (alchemist-execute--read-command alchemist-execute-command))) | |||
| (alchemist-buffer-run (alchemist-utils--build-runner-cmdlist cmdlist) | |||
| alchemist-execute-buffer-name)) | |||
| (provide 'alchemist-execute) | |||
| ;;; alchemist-execute.el ends here | |||
| @ -1,336 +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: | |||
| (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 | |||
| @ -1,204 +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-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 | |||
| @ -1,50 +0,0 @@ | |||
| ;;; alchemist-hooks.el --- Hooks functionality | |||
| ;; Copyright © 2014-2015 Samuel Tonini | |||
| ;; Author: Samuel Tonini <tonini.samuel@gmail.com | |||
| ;; Dave Thomas <http://pragdave.me> | |||
| ;; 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: | |||
| ;; Hooks functionality | |||
| ;;; Code: | |||
| (defgroup alchemist-hooks nil | |||
| "Hooks" | |||
| :prefix "alchemist-hooks-" | |||
| :group 'alchemist) | |||
| (defcustom alchemist-hooks-test-on-save nil | |||
| "If t, run `alchemist-mix-test' on save." | |||
| :type 'boolean | |||
| :group 'alchemist-hooks) | |||
| (defun alchemist-hooks--test-on-save () | |||
| (when (and alchemist-hooks-test-on-save | |||
| (alchemist-utils--elixir-project-root)) | |||
| (alchemist-mix-test))) | |||
| (eval-after-load 'elixir-mode | |||
| '(progn | |||
| (add-hook 'after-save-hook 'alchemist-hooks--test-on-save nil nil))) | |||
| (provide 'alchemist-hooks) | |||
| ;;; alchemist-hooks.el ends here | |||
| @ -1,201 +0,0 @@ | |||
| ;;; alchemist-help.el --- Interaction with an Elixir IEx process | |||
| ;; 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: | |||
| ;; Interaction with an Elixir IEx process | |||
| ;;; Code: | |||
| (require 'comint) | |||
| (defgroup alchemist-iex nil | |||
| "Interaction with an Elixir IEx process." | |||
| :prefix "alchemist-iex-" | |||
| :group 'alchemist) | |||
| (defcustom alchemist-iex-program-name "iex" | |||
| "The shell command for iex." | |||
| :type 'string | |||
| :group 'alchemist-iex) | |||
| (defcustom alchemist-iex-prompt-read-only t | |||
| "If non-nil, the prompt will be read-only." | |||
| :type 'boolean | |||
| :group 'alchemist-iex) | |||
| (defvar alchemist-iex-buffer nil | |||
| "The buffer in which the Elixir IEx process is running.") | |||
| (defvar alchemist-iex-mode-hook nil | |||
| "Hook for customizing `alchemist-iex-mode'.") | |||
| (defvar alchemist-iex-mode-map | |||
| (let ((map (nconc (make-sparse-keymap) comint-mode-map))) | |||
| (define-key map "\t" 'completion-at-point) | |||
| (define-key map (kbd (format "%s i r" alchemist-key-command-prefix)) 'alchemist-iex-open-input-ring) | |||
| (define-key map (kbd (format "%s i c" alchemist-key-command-prefix)) 'alchemist-iex-clear-buffer) | |||
| (define-key map (kbd (format "%s h e" alchemist-key-command-prefix)) 'alchemist-help-search-at-point) | |||
| (define-key map (kbd (format "%s h m" alchemist-key-command-prefix)) 'alchemist-help-search-marked-region) | |||
| (define-key map (kbd "M-.") 'alchemist-goto-definition-at-point) | |||
| map)) | |||
| (eval-after-load 'company | |||
| '(progn | |||
| (defun alchemist-iex--set-company-as-completion-at-point-function () | |||
| (setq completion-at-point-functions '(company-complete))) | |||
| (add-hook 'alchemist-iex-mode-hook 'alchemist-iex--set-company-as-completion-at-point-function))) | |||
| (define-derived-mode alchemist-iex-mode comint-mode "Alchemist-IEx" | |||
| "Major mode for interacting with an Elixir IEx process. | |||
| \\<alchemist-iex-mode-map>" | |||
| nil "Alchemist-IEx" | |||
| (set (make-local-variable 'comint-prompt-regexp) "^iex\(.+\)>") | |||
| (set (make-local-variable 'comint-prompt-read-only) alchemist-iex-prompt-read-only) | |||
| (set (make-local-variable 'comint-input-autoexpand) nil)) | |||
| (defun alchemist-iex-command (arg) | |||
| (split-string-and-unquote | |||
| (if (null arg) alchemist-iex-program-name | |||
| (read-string "Command to run Elixir IEx: " (concat alchemist-iex-program-name arg))))) | |||
| (defun alchemist-iex-start-process (command) | |||
| "Start an IEX process. | |||
| With universal prefix \\[universal-argument], prompts for a COMMAND, | |||
| otherwise uses `alchemist-iex-program-name'. | |||
| It runs the hook `alchemist-iex-mode-hook' after starting the process and | |||
| setting up the IEx buffer." | |||
| (interactive (list (alchemist-iex-command current-prefix-arg))) | |||
| (setq alchemist-iex-buffer | |||
| (apply 'make-comint "Alchemist-IEx" (car command) nil (cdr command))) | |||
| (with-current-buffer alchemist-iex-buffer | |||
| (alchemist-iex-mode) | |||
| (run-hooks 'alchemist-iex-mode-hook))) | |||
| (defun alchemist-iex-process (&optional arg) | |||
| (or (if (buffer-live-p alchemist-iex-buffer) | |||
| (get-buffer-process alchemist-iex-buffer)) | |||
| (progn | |||
| (let ((current-prefix-arg arg)) | |||
| (call-interactively 'alchemist-iex-start-process)) | |||
| (alchemist-iex-process arg)))) | |||
| (defun alchemist-iex--remove-newlines (string) | |||
| (replace-regexp-in-string "\n" " " string)) | |||
| (defun alchemist-iex-send-last-sexp () | |||
| "Send the previous sexp to the inferior IEx process." | |||
| (interactive) | |||
| (alchemist-iex-send-region (save-excursion (backward-sexp) (point)) (point))) | |||
| (defun alchemist-iex-send-current-line () | |||
| "Sends the current line to the IEx process." | |||
| (interactive) | |||
| (let ((str (thing-at-point 'line))) | |||
| (alchemist-iex--send-command (alchemist-iex-process) str))) | |||
| (defun alchemist-iex-send-current-line-and-go () | |||
| "Sends the current line to the inferior IEx process | |||
| and jump to the buffer." | |||
| (interactive) | |||
| (call-interactively 'alchemist-iex-send-current-line) | |||
| (pop-to-buffer (process-buffer (alchemist-iex-process)))) | |||
| (defun alchemist-iex-send-region-and-go () | |||
| "Sends the marked region to the inferior IEx process | |||
| and jump to the buffer." | |||
| (interactive) | |||
| (call-interactively 'alchemist-iex-send-region) | |||
| (pop-to-buffer (process-buffer (alchemist-iex-process)))) | |||
| (defun alchemist-iex-send-region (beg end) | |||
| "Sends the marked region to the IEx process." | |||
| (interactive (list (point) (mark))) | |||
| (unless (and beg end) | |||
| (error "The mark is not set now, so there is no region")) | |||
| (let* ((region (buffer-substring-no-properties beg end))) | |||
| (alchemist-iex--send-command (alchemist-iex-process) region))) | |||
| (defun alchemist-iex-compile-this-buffer () | |||
| "Compiles the current buffer in the IEx process." | |||
| (interactive) | |||
| (let ((str (format "c(\"%s\")" (buffer-file-name)))) | |||
| (alchemist-iex--send-command (alchemist-iex-process) str))) | |||
| (defun alchemist-iex-recompile-this-buffer () | |||
| "Recompiles and reloads the current buffer in the IEx process." | |||
| (interactive) | |||
| (let ((str (format "r(\"%s\")" (buffer-file-name)))) | |||
| (alchemist-iex--send-command (alchemist-iex-process) str))) | |||
| (defun alchemist-iex--send-command (proc str) | |||
| (let ((str-no-newline (concat (alchemist-iex--remove-newlines str) "\n")) | |||
| (str (concat str "\n"))) | |||
| (with-current-buffer (process-buffer proc) | |||
| (goto-char (process-mark proc)) | |||
| (insert-before-markers str) | |||
| (move-marker comint-last-input-end (point)) | |||
| (comint-send-string proc str-no-newline)))) | |||
| (defun alchemist-iex-clear-buffer () | |||
| "Clear the current iex process buffer." | |||
| (interactive) | |||
| (let ((comint-buffer-maximum-size 0)) | |||
| (comint-truncate-buffer))) | |||
| (defun alchemist-iex-open-input-ring () | |||
| "Open the buffer containing the input history." | |||
| (interactive) | |||
| (progn | |||
| (comint-dynamic-list-input-ring) | |||
| (other-window 1))) | |||
| ;;;###autoload | |||
| (defalias 'run-elixir 'alchemist-iex-run) | |||
| (defalias 'inferior-elixir 'alchemist-iex-run) | |||
| ;;;###autoload | |||
| (defun alchemist-iex-run (&optional arg) | |||
| "Start an IEx process. | |||
| Show the IEx buffer if an IEx process is already run." | |||
| (interactive "P") | |||
| (let ((proc (alchemist-iex-process arg))) | |||
| (pop-to-buffer (process-buffer proc)))) | |||
| ;;;###autoload | |||
| (defun alchemist-iex-project-run () | |||
| "Start an IEx process with mix 'iex -S mix' in the | |||
| context of an Elixir project. | |||
| Show the IEx buffer if an IEx process is already run." | |||
| (interactive) | |||
| (let ((old-directory default-directory)) | |||
| (if (alchemist-project-p) | |||
| (progn | |||
| (alchemist-project--establish-root-directory) | |||
| (let ((proc (alchemist-iex-process " -S mix"))) | |||
| (cd old-directory) | |||
| (pop-to-buffer (process-buffer proc)))) | |||
| (message "No mix.exs file available. Please use `alchemist-iex-run' instead.")))) | |||
| (provide 'alchemist-iex) | |||
| ;;; alchemist-iex.el ends here | |||
| @ -1,217 +0,0 @@ | |||
| ;;; alchemist-mix.el --- Emacs integration for Elixir's mix | |||
| ;; 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: | |||
| ;; Emacs integration for Elixir's mix | |||
| ;;; Code: | |||
| (defgroup alchemist-mix nil | |||
| "Emacs integration for Elixir's mix." | |||
| :prefix "alchemist-mix-" | |||
| :group 'alchemist) | |||
| ;; Variables | |||
| (defcustom alchemist-mix-command "mix" | |||
| "The shell command for mix." | |||
| :type 'string | |||
| :group 'alchemist-mix) | |||
| (defcustom alchemist-mix-test-default-options '("--exclude pending:true") | |||
| "Default options for alchemist test command." | |||
| :type '(repeat string) | |||
| :group 'alchemist-mix) | |||
| (defcustom alchemist-mix-env nil | |||
| "The default mix env to run mix commands with. If nil, the mix env is | |||
| not set explicitly." | |||
| :type '(string boolean) | |||
| :group 'alchemist-mix) | |||
| (defvar alchemist-mix-buffer-name "*mix*" | |||
| "Name of the mix output buffer.") | |||
| (defvar alchemist-mix--envs '("dev" "prod" "test") | |||
| "The list of mix envs to use as defaults.") | |||
| (defvar alchemist-mix--deps-commands | |||
| '("deps" "deps.clean" "deps.compile" "deps.get" "deps.unlock" "deps.unlock") | |||
| "List of all deps.* available commands.") | |||
| (defvar alchemist-mix--local-commands | |||
| '("local" "local.install" "local.rebar" "local.uninstall") | |||
| "List of all local.* available commands.") | |||
| (defvar alchemist-mix--local-install-option-types '("path" "url") | |||
| "List of local.install option types.") | |||
| ;; Private functions | |||
| (defun alchemist-mix--completing-read (prompt cmdlist) | |||
| (completing-read prompt cmdlist nil t nil nil (car cmdlist))) | |||
| (defun alchemist-mix--test-file (filename) | |||
| "Run a specific FILENAME as argument for the mix command test." | |||
| (when (not (file-exists-p filename)) | |||
| (error "The given file doesn't exists")) | |||
| (alchemist-mix-execute `("test" ,(expand-file-name filename) ,@alchemist-mix-test-default-options) | |||
| alchemist-test-mode-buffer-name)) | |||
| (defun alchemist-mix--commands () | |||
| (let ((mix-cmd-list (shell-command-to-string (format "%s help" alchemist-mix-command)))) | |||
| (mapcar (lambda (s) | |||
| (cdr (split-string (car (split-string s "#"))))) | |||
| (cdr (split-string mix-cmd-list "\n"))))) | |||
| ;; Public functions | |||
| (defun alchemist-mix-display-mix-buffer () | |||
| "Display the mix buffer when exists." | |||
| (interactive) | |||
| (when (get-buffer alchemist-mix-buffer-name) | |||
| (display-buffer alchemist-mix-buffer-name))) | |||
| (defun alchemist-mix-new (name) | |||
| "Create a new elixir project named by NAME." | |||
| (interactive "Gmix new: ") | |||
| (alchemist-mix-execute (list "new" (expand-file-name name)) | |||
| alchemist-mix-buffer-name)) | |||
| (defun alchemist-mix-test () | |||
| "Run the whole elixir test suite." | |||
| (interactive) | |||
| (alchemist-mix-execute `("test" ,@alchemist-mix-test-default-options) | |||
| alchemist-test-mode-buffer-name)) | |||
| (defun alchemist-mix-test-this-buffer () | |||
| "Run the current buffer through mix test." | |||
| (interactive) | |||
| (alchemist-mix--test-file buffer-file-name)) | |||
| (defun alchemist-mix-test-file (filename) | |||
| "Run `alchemist-mix--test-file' with the FILENAME." | |||
| (interactive "Fmix test: ") | |||
| (alchemist-mix--test-file (expand-file-name filename))) | |||
| (defun alchemist-mix-test-at-point () | |||
| "Run the test at point." | |||
| (interactive) | |||
| (let* ((line (line-number-at-pos (point))) | |||
| (file-and-line (format "%s:%s" buffer-file-name line))) | |||
| (alchemist-mix-execute (list "test" file-and-line) | |||
| alchemist-test-mode-buffer-name))) | |||
| (defun alchemist-mix-compile (command &optional prefix) | |||
| "Compile the whole elixir project. Prompt for the mix env if the prefix | |||
| arg is set." | |||
| (interactive "Mmix compile: \nP") | |||
| (alchemist-mix-execute (list "compile" command) | |||
| alchemist-mix-buffer-name prefix)) | |||
| (defun alchemist-mix-run (command &optional prefix) | |||
| "Runs the given file or expression in the context of the application. | |||
| Prompt for the mix env if the prefix arg is set." | |||
| (interactive "Mmix run: \nP") | |||
| (alchemist-mix-execute (list "run" command) | |||
| alchemist-mix-buffer-name prefix)) | |||
| (defun alchemist-mix-deps-with-prompt (command &optional prefix) | |||
| "Prompt for mix deps commands." | |||
| (interactive | |||
| (list (alchemist-mix--completing-read "mix deps: " alchemist-mix--deps-commands) | |||
| current-prefix-arg)) | |||
| (alchemist-mix-execute (list command) | |||
| alchemist-mix-buffer-name prefix)) | |||
| (defun alchemist-mix (command &optional prefix) | |||
| "Prompt for mix commands. Prompt for the mix env if the prefix arg is set." | |||
| (interactive | |||
| (list (alchemist-mix--completing-read "mix: " (alchemist-mix--commands)) | |||
| current-prefix-arg)) | |||
| (let ((command (read-string "mix " (concat command " ")))) | |||
| (alchemist-mix-execute (list command) | |||
| alchemist-mix-buffer-name prefix))) | |||
| (defun alchemist-mix-local-with-prompt (command) | |||
| "Prompt for mix local commands." | |||
| (interactive | |||
| (list (alchemist-mix--completing-read "mix local: " alchemist-mix--local-commands))) | |||
| (if (string= command "local.install") | |||
| (call-interactively 'alchemist-mix-local-install) | |||
| (alchemist-mix-execute (list command) | |||
| alchemist-mix-buffer-name))) | |||
| (defun alchemist-mix-local-install (path-or-url) | |||
| "Prompt for mix local.install PATH-OR-URL." | |||
| (interactive | |||
| (list (completing-read "mix local.install FORMAT: " | |||
| alchemist-mix--local-install-option-types | |||
| nil t nil nil (car alchemist-mix--local-install-option-types)))) | |||
| (if (string= path-or-url (car alchemist-mix--local-install-option-types)) | |||
| (call-interactively 'alchemist-mix-local-install-with-path) | |||
| (call-interactively 'alchemist-mix-local-install-with-url))) | |||
| (defun alchemist-mix-local-install-with-path (path) | |||
| "Runs local.install and prompt for a PATH as argument." | |||
| (interactive "fmix local.install PATH: ") | |||
| (alchemist-mix-execute (list "local.install" path) | |||
| alchemist-mix-buffer-name)) | |||
| (defun alchemist-mix-local-install-with-url (url) | |||
| "Runs local.install and prompt for a URL as argument." | |||
| (interactive "Mmix local.install URL: ") | |||
| (alchemist-mix-execute (list "local.install" url) | |||
| alchemist-mix-buffer-name)) | |||
| (defun alchemist-mix-hex-search (command &optional prefix) | |||
| "Display packages matching the given search query. Prompt for the mix env | |||
| if the prefix arg is set." | |||
| (interactive "Mmix hex.search: \nP") | |||
| (alchemist-mix-execute (list "hex.search" command) | |||
| alchemist-mix-buffer-name prefix)) | |||
| (defun alchemist-mix-help (command &optional prefix) | |||
| "Show help output for a specific mix command. Prompt for the mix env if | |||
| the prefix arg is set." | |||
| (interactive "Mmix help: \nP") | |||
| (alchemist-mix-execute (list "help" command) | |||
| alchemist-mix-buffer-name prefix)) | |||
| (defun alchemist-mix-execute (cmdlist buffer-name &optional prefix) | |||
| "Run a mix command. Prompt for the mix env if the prefix arg is set." | |||
| (interactive "Mmix: \nP") | |||
| (let ((old-directory default-directory) | |||
| (mix-env (if prefix | |||
| (completing-read "mix env: " | |||
| alchemist-mix--envs nil nil alchemist-mix-env) | |||
| alchemist-mix-env))) | |||
| (alchemist-project--establish-root-directory) | |||
| (alchemist-buffer-run (alchemist-utils--build-runner-cmdlist | |||
| (list (if mix-env (concat "MIX_ENV=" mix-env) "") | |||
| alchemist-mix-command cmdlist)) | |||
| buffer-name) | |||
| (cd old-directory))) | |||
| (provide 'alchemist-mix) | |||
| ;;; alchemist-mix.el ends here | |||
| @ -1,7 +0,0 @@ | |||
| (define-package "alchemist" "20150624.159" "Elixir tooling integration into Emacs" | |||
| '((emacs "24")) | |||
| :url "http://www.github.com/tonini/alchemist.el" :keywords | |||
| '("languages" "mix" "elixir" "elixirc" "hex")) | |||
| ;; Local Variables: | |||
| ;; no-byte-compile: t | |||
| ;; End: | |||
| @ -1,163 +0,0 @@ | |||
| ;;; alchemist-project.el --- API to identify Elixir mix projects. | |||
| ;; 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: | |||
| ;; API to identify Elixir mix projects. | |||
| ;;; Code: | |||
| (require 'cl) | |||
| (defgroup alchemist-project nil | |||
| "API to identify Elixir mix projects." | |||
| :prefix "alchemist-help-" | |||
| :group 'alchemist) | |||
| (defvar alchemist-project-root-indicators | |||
| '("mix.exs") | |||
| "list of file-/directory-names which indicate a root of a elixir project") | |||
| (defvar alchemist-project-deps-indicators | |||
| '(".hex") | |||
| "list of file-/directory-names which indicate a root of a elixir project") | |||
| (defun alchemist-project-p () | |||
| "Returns whether alchemist has access to a elixir project root or not" | |||
| (stringp (alchemist-project-root))) | |||
| (defun alchemist-project-parent-directory (a-directory) | |||
| "Returns the directory of which a-directory is a child" | |||
| (file-name-directory (directory-file-name a-directory))) | |||
| (defun alchemist-project-root-directory-p (a-directory) | |||
| "Returns t if a-directory is the root" | |||
| (equal a-directory (alchemist-project-parent-directory a-directory))) | |||
| (defun alchemist-project-root (&optional directory) | |||
| "Finds the root directory of the project by walking the directory tree until it finds a project root indicator." | |||
| (let* ((directory (file-name-as-directory (or directory (expand-file-name default-directory)))) | |||
| (present-files (directory-files directory))) | |||
| (cond ((alchemist-project-root-directory-p directory) nil) | |||
| ((> (length (intersection present-files alchemist-project-deps-indicators :test 'string=)) 0) | |||
| (alchemist-project-root (file-name-directory (directory-file-name directory)))) | |||
| ((> (length (intersection present-files alchemist-project-root-indicators :test 'string=)) 0) directory) | |||
| (t (alchemist-project-root (file-name-directory (directory-file-name directory))))))) | |||
| (defun alchemist-project--establish-root-directory () | |||
| "Set the default-directory to the Elixir project root." | |||
| (let ((project-root (alchemist-project-root))) | |||
| (when project-root | |||
| (setq default-directory project-root)))) | |||
| (defun alchemist-project-toggle-file-and-tests-other-window () | |||
| "Toggle between a file and its tests in other window." | |||
| (interactive) | |||
| (if (alchemist-utils--is-test-file-p) | |||
| (alchemist--project-open-file-for-current-tests 'find-file-other-window) | |||
| (alchemist--project-open-tests-for-current-file 'find-file-other-window))) | |||
| (defun alchemist-project-toggle-file-and-tests () | |||
| "Toggle between a file and its tests in the current window." | |||
| (interactive) | |||
| (if (alchemist-utils--is-test-file-p) | |||
| (alchemist--project-open-file-for-current-tests 'find-file) | |||
| (alchemist--project-open-tests-for-current-file 'find-file))) | |||
| (defun alchemist--project-open-file-for-current-tests (toggler) | |||
| "Open the appropriate implementation file for the current buffer by calling TOGGLER with filename." | |||
| (let* ((filename (file-relative-name (buffer-file-name) (alchemist-project-root))) | |||
| (filename (replace-regexp-in-string "^test/" "lib/" filename)) | |||
| (filename (replace-regexp-in-string "_test\.exs$" "\.ex" filename)) | |||
| (filename (format "%s/%s" (alchemist-project-root) filename))) | |||
| (funcall toggler filename))) | |||
| (defun alchemist--project-open-tests-for-current-file (toggler) | |||
| "Opens the appropriate test file by calling TOGGLER with filename." | |||
| (let* ((filename (file-relative-name (buffer-file-name) (alchemist-project-root))) | |||
| (filename (replace-regexp-in-string "^lib/" "test/" filename)) | |||
| (filename (replace-regexp-in-string "\.ex$" "_test\.exs" filename)) | |||
| (filename (format "%s/%s" (alchemist-project-root) filename))) | |||
| (if (file-exists-p filename) | |||
| (funcall toggler filename) | |||
| (if (y-or-n-p "No test file found; create one now?") | |||
| (alchemist-project--create-test-for-current-file | |||
| filename (current-buffer)) | |||
| (message "No test file found."))))) | |||
| (defun alchemist-project--create-test-for-current-file (filename buffer) | |||
| "Creates and populates a test module, FILENAME, for the code in BUFFER. | |||
| The module name given to the test module is determined from the name of the | |||
| first module defined in BUFFER." | |||
| (let* ((directory-name (file-name-directory filename)) | |||
| (module-name (alchemist-project--grok-module-name buffer)) | |||
| (test-module-name (concat module-name "Test"))) | |||
| (unless (file-exists-p directory-name) | |||
| (make-directory (file-name-directory filename) t)) | |||
| (alchemist-project--insert-test-boilerplate | |||
| (find-file-other-window filename) test-module-name))) | |||
| (defun alchemist-project--grok-module-name (buffer) | |||
| "Determines the name of the first module defined in BUFFER." | |||
| (save-excursion | |||
| (set-buffer buffer) | |||
| (goto-line 1) | |||
| (re-search-forward "defmodule\\s-\\(.+?\\)\\s-?,?\\s-do") | |||
| (match-string 1))) | |||
| (defun alchemist-project--insert-test-boilerplate (buffer module) | |||
| "Inserts ExUnit boilerplate for MODULE in BUFFER. | |||
| Point is left in a convenient location." | |||
| (set-buffer buffer) | |||
| (insert (concat "defmodule " module " do\n" | |||
| " use ExUnit.Case\n" | |||
| "\n" | |||
| "end\n")) | |||
| (goto-char (point-min)) | |||
| (beginning-of-line 3)) | |||
| (defun alchemist-project-find-test () | |||
| "Open project test directory and list all test files." | |||
| (interactive) | |||
| (when (alchemist-project-p) | |||
| (find-file (alchemist-project--open-directory-files "test")))) | |||
| (defun alchemist-project--open-directory-files (directory) | |||
| (let ((directory (concat (replace-regexp-in-string "\/?$" "" (concat (alchemist-project-root) directory) "/")))) | |||
| (message directory) | |||
| (concat directory "/" (completing-read (concat directory ": ") | |||
| (mapcar (lambda (path) | |||
| (replace-regexp-in-string (concat "^" (regexp-quote directory) "/") "" path)) | |||
| (split-string | |||
| (shell-command-to-string | |||
| (concat | |||
| "find \"" directory | |||
| "\" -type f | grep \"_test\.exs\" | grep -v \"/.git/\"")))))))) | |||
| (defun alchemist-project-name () | |||
| "Return the name of the current Elixir project." | |||
| (if (alchemist-project-p) | |||
| (car (cdr (reverse (split-string (alchemist-project-root) "/")))) | |||
| "")) | |||
| (provide 'alchemist-project) | |||
| ;;; alchemist-project.el ends here | |||
| @ -1,308 +0,0 @@ | |||
| ;;; 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 | |||
| @ -1,169 +0,0 @@ | |||
| ;;; 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 | |||
| @ -1,94 +0,0 @@ | |||
| ;;; alchemist-utils.el --- | |||
| ;; 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: | |||
| ;;; Code: | |||
| (require 'ansi-color) | |||
| (defvar alchemist-utils--elixir-project-root-indicator | |||
| "mix.exs" | |||
| "The file which indicate an elixir project root.") | |||
| (defun alchemist-utils--elixir-project-root () | |||
| "Finds the root directory of the project. | |||
| It walks the directory tree until it finds a elixir project root indicator." | |||
| (let* ((file (file-name-as-directory (expand-file-name default-directory)))) | |||
| (locate-dominating-file file alchemist-utils--elixir-project-root-indicator))) | |||
| (defun alchemist-utils--flatten (alist) | |||
| (cond ((null alist) nil) | |||
| ((atom alist) (list alist)) | |||
| (t (append (alchemist-utils--flatten (car alist)) | |||
| (alchemist-utils--flatten (cdr alist)))))) | |||
| (defun alchemist-utils--build-runner-cmdlist (command) | |||
| "Build the commands list for the runner." | |||
| (remove "" (alchemist-utils--flatten | |||
| (list (if (stringp command) | |||
| (split-string command) | |||
| command))))) | |||
| (defun alchemist-utils--clear-search-text (search-text) | |||
| (let* ((search-text (replace-regexp-in-string "\\.$" "" search-text)) | |||
| (search-text (replace-regexp-in-string "^\\.$" "" search-text)) | |||
| (search-text (replace-regexp-in-string ",$" "" search-text)) | |||
| (search-text (replace-regexp-in-string "^,$" "" search-text))) | |||
| search-text)) | |||
| (defun alchemist-utils--erase-buffer (buffer) | |||
| "Use `erase-buffer' inside BUFFER." | |||
| (with-current-buffer buffer | |||
| (erase-buffer))) | |||
| (defun alchemist-utils--get-buffer-content (buffer) | |||
| "Return the content of BUFFER." | |||
| (with-current-buffer buffer | |||
| (buffer-substring (point-min) (point-max)))) | |||
| (defun alchemist--utils-clear-ansi-sequences (string) | |||
| "Clear STRING from all ansi escape sequences." | |||
| (ansi-color-filter-apply string)) | |||
| (defun alchemist-utils--remove-newline-at-end (string) | |||
| (replace-regexp-in-string "\n$" "" string)) | |||
| (defun alchemist-utils--count-char-in-str (regexp str) | |||
| (loop with start = 0 | |||
| for count from 0 | |||
| while (string-match regexp str start) | |||
| do (setq start (match-end 0)) | |||
| finally return count)) | |||
| (defun alchemist-utils--is-test-file-p () | |||
| "Check wether the visited file is a test file." | |||
| (string-match "_test\.exs$" (or (buffer-file-name) ""))) | |||
| (defun alchemist-utils--empty-string-p (string) | |||
| (or (null string) | |||
| (let* ((string (replace-regexp-in-string "^\s+" "" string )) | |||
| (string (replace-regexp-in-string "\s+$" "" string))) | |||
| (string= string "")))) | |||
| (provide 'alchemist-utils) | |||
| ;;; alchemist-utils.el ends here | |||
| @ -1,224 +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: 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 | |||
| @ -1,96 +0,0 @@ | |||
| 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 | |||
| @ -1,340 +0,0 @@ | |||
| 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 | |||
| @ -1,55 +0,0 @@ | |||
| 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 | |||
| @ -1,92 +0,0 @@ | |||
| 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]) | |||
| @ -1,46 +0,0 @@ | |||
| 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 | |||
| @ -1,6 +0,0 @@ | |||
| defmodule Alchemist.Utils do | |||
| def clear_context_list(modules) do | |||
| cleared = Regex.replace ~r/\.\]/, modules, "]" | |||
| Regex.replace ~r/\.\,/, cleared, "," | |||
| end | |||
| end | |||
| @ -0,0 +1,116 @@ | |||
| ;;; alchemist-autoloads.el --- automatically extracted autoloads | |||
| ;; | |||
| ;;; Code: | |||
| (add-to-list 'load-path (or (file-name-directory #$) (car load-path))) | |||
| ;;;### (autoloads nil "alchemist" "alchemist.el" (22171 46586 0 0)) | |||
| ;;; Generated autoloads from alchemist.el | |||
| (autoload 'alchemist-mode "alchemist" "\ | |||
| Toggle alchemist mode. | |||
| Key bindings: | |||
| \\{alchemist-mode-map} | |||
| \(fn &optional ARG)" t nil) | |||
| ;;;*** | |||
| ;;;### (autoloads nil "alchemist-iex" "alchemist-iex.el" (22171 46586 | |||
| ;;;;;; 0 0)) | |||
| ;;; Generated autoloads from alchemist-iex.el | |||
| (defalias 'run-elixir 'alchemist-iex-run) | |||
| (autoload 'alchemist-iex-run "alchemist-iex" "\ | |||
| Start an IEx process. | |||
| Show the IEx buffer if an IEx process is already run. | |||
| \(fn &optional ARG)" t nil) | |||
| (autoload 'alchemist-iex-project-run "alchemist-iex" "\ | |||
| Start an IEx process with mix 'iex -S mix' in the | |||
| context of an Elixir project. | |||
| Show the IEx buffer if an IEx process is already run. | |||
| \(fn)" t nil) | |||
| ;;;*** | |||
| ;;;### (autoloads nil "alchemist-phoenix" "alchemist-phoenix.el" | |||
| ;;;;;; (22171 46586 0 0)) | |||
| ;;; Generated autoloads from alchemist-phoenix.el | |||
| (autoload 'alchemist-phoenix-project-p "alchemist-phoenix" "\ | |||
| Return non-nil if `default-directory' is inside an Phoenix project. | |||
| \(fn)" nil nil) | |||
| (autoload 'alchemist-phoenix-mode "alchemist-phoenix" "\ | |||
| Minor mode for Elixir Phoenix web framework projects. | |||
| The following commands are available: | |||
| \\{alchemist-phoenix-mode-map} | |||
| \(fn &optional ARG)" t nil) | |||
| (autoload 'alchemist-phoenix-enable-mode "alchemist-phoenix" "\ | |||
| \(fn)" nil nil) | |||
| (dolist (hook '(alchemist-mode-hook)) (add-hook hook 'alchemist-phoenix-enable-mode)) | |||
| ;;;*** | |||
| ;;;### (autoloads nil "alchemist-refcard" "alchemist-refcard.el" | |||
| ;;;;;; (22171 46586 0 0)) | |||
| ;;; Generated autoloads from alchemist-refcard.el | |||
| (autoload 'alchemist-refcard "alchemist-refcard" "\ | |||
| Generate an Alchemist refcard of all the features. | |||
| \(fn)" t nil) | |||
| ;;;*** | |||
| ;;;### (autoloads nil "alchemist-test-mode" "alchemist-test-mode.el" | |||
| ;;;;;; (22171 46586 0 0)) | |||
| ;;; Generated autoloads from alchemist-test-mode.el | |||
| (autoload 'alchemist-test-mode "alchemist-test-mode" "\ | |||
| Minor mode for Elixir ExUnit files. | |||
| The following commands are available: | |||
| \\{alchemist-test-mode-map} | |||
| \(fn &optional ARG)" t nil) | |||
| (autoload 'alchemist-test-enable-mode "alchemist-test-mode" "\ | |||
| \(fn)" nil nil) | |||
| (dolist (hook '(alchemist-mode-hook)) (add-hook hook 'alchemist-test-enable-mode)) | |||
| ;;;*** | |||
| ;;;### (autoloads nil nil ("alchemist-company.el" "alchemist-compile.el" | |||
| ;;;;;; "alchemist-complete.el" "alchemist-eval.el" "alchemist-execute.el" | |||
| ;;;;;; "alchemist-file.el" "alchemist-goto.el" "alchemist-help.el" | |||
| ;;;;;; "alchemist-hooks.el" "alchemist-info.el" "alchemist-interact.el" | |||
| ;;;;;; "alchemist-key.el" "alchemist-macroexpand.el" "alchemist-message.el" | |||
| ;;;;;; "alchemist-mix.el" "alchemist-pkg.el" "alchemist-project.el" | |||
| ;;;;;; "alchemist-report.el" "alchemist-scope.el" "alchemist-server.el" | |||
| ;;;;;; "alchemist-utils.el") (22171 46586 526902 0)) | |||
| ;;;*** | |||
| ;; Local Variables: | |||
| ;; version-control: never | |||
| ;; no-byte-compile: t | |||
| ;; no-update-autoloads: t | |||
| ;; End: | |||
| ;;; alchemist-autoloads.el ends here | |||
| @ -0,0 +1,146 @@ | |||
| ;;; alchemist-company.el --- Elixir company-mode backend -*- 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: | |||
| ;; Elixir company-mode backend. | |||
| ;;; Code: | |||
| (require 'cl-lib) | |||
| (require 'company) | |||
| (require 'alchemist-help) | |||
| (require 'alchemist-goto) | |||
| (require 'alchemist-scope) | |||
| (require 'alchemist-server) | |||
| (require 'alchemist-complete) | |||
| (defgroup alchemist-company nil | |||
| "Elixir company-mode backend." | |||
| :prefix "alchemist-company-" | |||
| :group 'alchemist) | |||
| ;; Variables | |||
| (defcustom alchemist-company-show-annotation t | |||
| "Show an annotation inline with the candidate." | |||
| :type 'boolean | |||
| :group 'alchemist-company) | |||
| (defvar alchemist-company-callback nil) | |||
| (defvar alchemist-company-filter-output nil) | |||
| (defvar alchemist-company-last-completion nil) | |||
| (defvar alchemist-company-doc-lookup-done nil) | |||
| (defun alchemist-company--wait-for-doc-buffer () | |||
| (while (not alchemist-company-doc-lookup-done) | |||
| (sit-for 0.01))) | |||
| (defun alchemist-company-show-documentation (candidate) | |||
| (interactive) | |||
| (let* ((annotation (alchemist-company--annotation candidate)) | |||
| (candidate (if annotation | |||
| (format "%s%s" candidate annotation) | |||
| candidate)) | |||
| (candidate (alchemist-help--prepare-search-expr candidate))) | |||
| (setq alchemist-company-doc-lookup-done nil) | |||
| (alchemist-server-help (alchemist-help--server-arguments candidate) #'alchemist-company-doc-buffer-filter) | |||
| (alchemist-company--wait-for-doc-buffer) | |||
| (get-buffer alchemist-help-buffer-name))) | |||
| (defun alchemist-company-open-definition (candidate) | |||
| (interactive) | |||
| (alchemist-goto--open-definition candidate)) | |||
| (defun alchemist-company--annotation (candidate) | |||
| (get-text-property 0 'meta candidate)) | |||
| (defun alchemist-company-build-scope-arg (arg) | |||
| "Build informations about the current context." | |||
| (let* ((modules (alchemist-utils-prepare-modules-for-elixir | |||
| (alchemist-scope-all-modules))) | |||
| (aliases (alchemist-utils-prepare-aliases-for-elixir | |||
| (alchemist-scope-aliases)))) | |||
| (format "{ \"%s\", [ context: Elixir, imports: %s, aliases: %s ] }" arg modules aliases))) | |||
| (defun alchemist-company-build-server-arg (arg) | |||
| (if (not (equal major-mode 'alchemist-iex-mode)) | |||
| (alchemist-company-build-scope-arg arg) | |||
| (format "{ \"%s\", [ context: [], imports: [], aliases: [] ] }" arg))) | |||
| (defun alchemist-company-filter (_process output) | |||
| (setq alchemist-company-filter-output (cons output alchemist-company-filter-output)) | |||
| (if (alchemist-server-contains-end-marker-p output) | |||
| (let* ((candidates (alchemist-complete--build-candidates-from-process-output alchemist-company-filter-output))) | |||
| (setq alchemist-company-filter-output nil) | |||
| (alchemist-company-serve-candidates-to-callback (-distinct candidates))))) | |||
| (defun alchemist-company-doc-buffer-filter (_process output) | |||
| (setq alchemist-company-filter-output (cons output alchemist-company-filter-output)) | |||
| (if (alchemist-server-contains-end-marker-p output) | |||
| (let ((string (alchemist-server-prepare-filter-output alchemist-company-filter-output))) | |||
| (setq alchemist-company-filter-output nil) | |||
| (if (get-buffer alchemist-help-buffer-name) | |||
| (kill-buffer alchemist-help-buffer-name)) | |||
| (with-current-buffer (get-buffer-create alchemist-help-buffer-name) | |||
| (insert string) | |||
| (ansi-color-apply-on-region (point-min) (point-max)) | |||
| (alchemist-help-minor-mode 1)) | |||
| (setq alchemist-company-doc-lookup-done t)))) | |||
| (defun alchemist-company-serve-candidates-to-callback (candidates) | |||
| (let* ((candidates (if candidates | |||
| candidates | |||
| (alchemsit-complete--dabbrev-code-candidates))) | |||
| (candidates (if (eq (length candidates) 1) | |||
| (-insert-at 0 company-prefix candidates) | |||
| candidates)) | |||
| (candidates (-distinct candidates))) | |||
| (funcall alchemist-company-callback candidates))) | |||
| (defun alchemist-company (command &optional arg &rest ignored) | |||
| "`company-mode' completion back-end for Elixir." | |||
| (interactive (list 'interactive)) | |||
| (when alchemist-company-show-annotation | |||
| (set 'company-tooltip-align-annotations t)) | |||
| (cl-case command | |||
| (interactive (company-begin-backend 'alchemist-company)) | |||
| (init (or (eq major-mode 'elixir-mode) | |||
| (string= mode-name "Alchemist-IEx"))) | |||
| (prefix (and (or (eq major-mode 'elixir-mode) | |||
| (string= mode-name "Alchemist-IEx")) | |||
| (alchemist-scope-expression))) | |||
| (doc-buffer (alchemist-company-show-documentation arg)) | |||
| (location (alchemist-company-open-definition arg)) | |||
| (candidates (cons :async | |||
| (lambda (cb) | |||
| (setq alchemist-company-last-completion arg) | |||
| (setq alchemist-company-callback cb) | |||
| (alchemist-server-complete-candidates (alchemist-company-build-server-arg arg) | |||
| #'alchemist-company-filter)))) | |||
| (annotation (when alchemist-company-show-annotation | |||
| (alchemist-company--annotation arg))))) | |||
| (add-to-list 'company-backends 'alchemist-company) | |||
| (provide 'alchemist-company) | |||
| ;;; alchemist-company.el ends here | |||
| @ -0,0 +1,89 @@ | |||
| ;;; alchemist-compile.el --- Elixir compilation functionality | |||
| ;; 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: | |||
| ;; Elixir compilation functionality. | |||
| ;;; Code: | |||
| (require 'alchemist-utils) | |||
| (require 'alchemist-report) | |||
| (defgroup alchemist-compile nil | |||
| "Elixir compilation functionality." | |||
| :prefix "alchemist-compile-" | |||
| :group 'alchemist) | |||
| ;; Variables | |||
| (defcustom alchemist-compile-command "elixirc" | |||
| "The shell command for elixirc." | |||
| :type 'string | |||
| :group 'alchemist-compile) | |||
| (defvar alchemist-compile-buffer-name "*alchemist elixirc*" | |||
| "Name of the elixir output buffer.") | |||
| (defvar alchemist-compile-mode-map | |||
| (let ((map (make-sparse-keymap))) | |||
| (define-key map "q" #'quit-window) | |||
| map)) | |||
| ;; Private functions | |||
| (defun alchemist-compile--file (filename) | |||
| (cond ((not (file-exists-p filename)) (error "The given file doesn't exist")) | |||
| ((string-match "\.exs$" filename) (error "The given file is an Elixir Script")) | |||
| (t (alchemist-compile (list alchemist-compile-command (expand-file-name filename)))))) | |||
| (defun alchemist-compile--read-command (command) | |||
| (read-shell-command "elixirc command: " (concat command " "))) | |||
| ;; Public functions | |||
| (defun alchemist-compile-this-buffer () | |||
| "Compile the current buffer with elixirc." | |||
| (interactive) | |||
| (alchemist-compile--file buffer-file-name)) | |||
| (defun alchemist-compile-file (filename) | |||
| "Compile the given FILENAME." | |||
| (interactive "Felixirc: ") | |||
| (alchemist-compile--file (expand-file-name filename))) | |||
| (define-derived-mode alchemist-compile-mode fundamental-mode "Elixir Compile Mode" | |||
| "Major mode for compiling Elixir files. | |||
| \\{alchemist-compile-mode-map}" | |||
| (setq buffer-read-only t) | |||
| (setq-local truncate-lines t) | |||
| (setq-local electric-indent-chars nil)) | |||
| (defun alchemist-compile (cmdlist) | |||
| "Compile CMDLIST with elixirc." | |||
| (interactive (list (alchemist-compile--read-command alchemist-compile-command))) | |||
| (let ((command (alchemist-utils-build-command cmdlist))) | |||
| (alchemist-report-run command "alchemist-compile-report" alchemist-compile-buffer-name 'alchemist-compile-mode))) | |||
| (provide 'alchemist-compile) | |||
| ;;; alchemist-compile.el ends here | |||
| @ -0,0 +1,142 @@ | |||
| ;;; alchemist-complete.el --- Complete functionality for Elixir source code -*- 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: | |||
| ;; Complete functionality for Elixir and Erlang source code. | |||
| ;;; Code: | |||
| (require 'cl-lib) | |||
| (require 'dash) | |||
| (require 'ansi-color) | |||
| (require 'company-dabbrev-code) | |||
| (require 'alchemist-utils) | |||
| (defgroup alchemist-complete nil | |||
| "Complete functionality for Elixir source code." | |||
| :prefix "alchemist-complete-" | |||
| :group 'alchemist) | |||
| (defvar alchemist-company-last-completion nil) | |||
| (defun alchemist-complete--concat-prefix-with-functions (prefix functions &optional add-prefix) | |||
| (let* ((prefix (mapconcat 'concat (butlast (split-string prefix "\\.") 1) ".")) | |||
| (candidates (-map (lambda (c) (concat prefix "." c)) (cdr functions)))) | |||
| (if add-prefix | |||
| (push prefix candidates) | |||
| candidates))) | |||
| (defun alchemist-complete--add-prefix-to-function (prefix function) | |||
| (let* ((prefix (mapconcat 'concat (butlast (split-string prefix "\\.") 1) ".")) | |||
| (candidate (concat prefix "." function))) | |||
| candidate)) | |||
| (defun alchemist-complete--build-candidates (a-list) | |||
| (let* ((search-term (car a-list)) | |||
| (candidates a-list) | |||
| (candidates (-map (lambda (f) | |||
| (let* ((candidate f) | |||
| (meta (if (string-match-p "^.+/" f) | |||
| (replace-regexp-in-string "^.+/" "/" f) | |||
| ""))) | |||
| (cond | |||
| ((and (string-match-p "^:" search-term) | |||
| (not (string-match-p "\\." search-term))) | |||
| (unless (string= search-term candidate) | |||
| (propertize (concat ":" candidate)) | |||
| )) | |||
| ((string-match-p "\\." search-term) | |||
| (unless (string= search-term candidate) | |||
| (propertize (alchemist-complete--add-prefix-to-function (concat (alchemist-scope-extract-module search-term) ".") | |||
| (replace-regexp-in-string "/[0-9]$" "" candidate)) 'meta meta))) | |||
| (t (propertize (replace-regexp-in-string "/[0-9]$" "" candidate) 'meta meta))))) | |||
| candidates)) | |||
| (candidates (-remove 'null candidates))) | |||
| (cond | |||
| ((and (string-match-p "\\.$" search-term) | |||
| (not (string-match-p "\\.$" alchemist-company-last-completion))) | |||
| (push (alchemist-utils-remove-dot-at-the-end search-term) candidates)) | |||
| (t candidates)))) | |||
| (defun alchemist-complete--build-help-candidates (a-list) | |||
| (let* ((search-term (car a-list)) | |||
| (candidates (cond ((> (alchemist-utils-count-char-occurence "\\." search-term) 1) | |||
| (let ((search (if (string-match-p "\\.[a-z0-9_\?!]+$" search-term) | |||
| (list (replace-regexp-in-string "\\.[a-z0-9_\?!]+$" "" search-term)) | |||
| (list (alchemist-utils-remove-dot-at-the-end search-term)))) | |||
| (candidates (-map (lambda (c) | |||
| (if (string-match-p "\\.[a-z0-9_\?!]+$" search-term) | |||
| (concat (replace-regexp-in-string "\\.[a-z0-9_\?!]+$" "." search-term) c) | |||
| (concat search-term c))) | |||
| (cdr a-list)))) | |||
| (append search candidates))) | |||
| ((string-match-p "\\.$" search-term) | |||
| (alchemist-complete--concat-prefix-with-functions search-term a-list t)) | |||
| ((string-match-p "\\.[a-z0-9_\?!]+$" search-term) | |||
| (alchemist-complete--concat-prefix-with-functions search-term a-list)) | |||
| (t | |||
| a-list)))) | |||
| (-distinct candidates))) | |||
| (defun alchemist-complete--output-to-list (output) | |||
| (let* ((output (split-string output))) | |||
| (-remove 'null output))) | |||
| (defun alchemist-complete--build-candidates-from-process-output (output) | |||
| (let* ((output (alchemist-server-prepare-filter-output output)) | |||
| (candidates (if (not (alchemist-utils-empty-string-p output)) | |||
| (alchemist-complete--output-to-list | |||
| (ansi-color-filter-apply output)) | |||
| '())) | |||
| (candidates (if candidates | |||
| (alchemist-complete--build-candidates candidates) | |||
| '()))) | |||
| candidates)) | |||
| (defun alchemist-complete--completing-prompt (initial completing-collection) | |||
| (let* ((completing-collection (alchemist-complete--build-help-candidates completing-collection))) | |||
| (cond ((equal (length completing-collection) 1) | |||
| (car completing-collection)) | |||
| (completing-collection | |||
| (completing-read | |||
| "Elixir help: " | |||
| completing-collection | |||
| nil | |||
| nil | |||
| (replace-regexp-in-string "\\.$" "" initial))) | |||
| (t initial)))) | |||
| (defun alchemsit-complete--dabbrev-code-candidates () | |||
| "This function uses a piece of functionality of company-dabbrev-code backend. | |||
| Please have a look at the company-dabbrev-code function for more | |||
| detailed information." | |||
| (let ((case-fold-search nil)) | |||
| (-distinct (company-dabbrev--search | |||
| (company-dabbrev-code--make-regexp alchemist-company-last-completion) | |||
| company-dabbrev-code-time-limit | |||
| (list major-mode) | |||
| t)))) | |||
| (provide 'alchemist-complete) | |||
| ;;; alchemist-complete.el ends here | |||
| @ -0,0 +1,219 @@ | |||
| ;;; alchemist-eval.el --- Elixir code inline evaluation functionality | |||
| ;; 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: | |||
| ;; Elixir code inline evaluation functionality | |||
| ;;; Code: | |||
| (require 'elixir-mode) | |||
| (require 'alchemist-server) | |||
| (require 'alchemist-interact) | |||
| (defgroup alchemist-eval nil | |||
| "Elixir code inline evaluation functionality." | |||
| :prefix "alchemist-eval-" | |||
| :group 'alchemist) | |||
| (defconst alchemist-eval-buffer-name "*alchemist-eval-mode*" | |||
| "Name of the Elixir evaluation buffer.") | |||
| (defvar alchemist-eval-mode-map | |||
| (let ((map (make-sparse-keymap))) | |||
| (define-key map (kbd "q") #'quit-window) | |||
| map) | |||
| "Keymap for `alchemist-eval-mode'.") | |||
| (defvar alchemist-eval-filter-output nil) | |||
| ;; Private functions | |||
| (defun alchemist-eval--insert (string) | |||
| (let ((lines (split-string string "\n"))) | |||
| (if (> (length lines) 1) | |||
| (progn | |||
| (save-excursion | |||
| (end-of-line) | |||
| (mapc (lambda (s) | |||
| (newline) | |||
| (insert (format "# => %s" s)) | |||
| (indent-according-to-mode)) | |||
| lines))) | |||
| (save-excursion | |||
| (end-of-line) | |||
| (insert (format " # => %s" string)))))) | |||
| (defun alchemist-eval--expression (expression) | |||
| (let ((file (make-temp-file "alchemist-eval" nil ".exs"))) | |||
| (with-temp-file file | |||
| (insert expression)) | |||
| (alchemist-server-eval (format "{ :eval, '%s' }" file) #'alchemist-eval-filter))) | |||
| (defun alchemist-eval--expression-and-print (expression) | |||
| (let ((file (make-temp-file "alchemist-eval" nil ".exs"))) | |||
| (with-temp-file file | |||
| (insert expression)) | |||
| (alchemist-server-eval (format "{ :eval, '%s' }" file) #'alchemist-eval-insert-filter))) | |||
| (defun alchemist-eval--quote-expression (expression) | |||
| (let ((file (make-temp-file "alchemist-eval" nil ".exs"))) | |||
| (with-temp-file file | |||
| (insert expression)) | |||
| (alchemist-server-eval (format "{ :quote, '%s' }" file) #'alchemist-eval-quoted-filter))) | |||
| (defun alchemist-eval--quote-expression-and-print (expression) | |||
| (let ((file (make-temp-file "alchemist-eval" nil ".exs"))) | |||
| (with-temp-file file | |||
| (insert expression)) | |||
| (alchemist-server-eval (format "{ :quote, '%s' }" file) #'alchemist-eval-quoted-insert-filter))) | |||
| (defun alchemist-eval-filter (_process output) | |||
| (setq alchemist-eval-filter-output (cons output alchemist-eval-filter-output)) | |||
| (when (alchemist-server-contains-end-marker-p output) | |||
| (alchemist-interact-create-popup alchemist-eval-buffer-name | |||
| (alchemist-server-prepare-filter-output alchemist-eval-filter-output) | |||
| #'(lambda () | |||
| (elixir-mode) | |||
| (alchemist-eval-mode) | |||
| (ansi-color-apply-on-region (point-min) (point-max)))) | |||
| (setq alchemist-eval-filter-output nil))) | |||
| (defun alchemist-eval-insert-filter (_process output) | |||
| (setq alchemist-eval-filter-output (cons output alchemist-eval-filter-output)) | |||
| (when (alchemist-server-contains-end-marker-p output) | |||
| (alchemist-interact-insert-as-comment | |||
| (alchemist-server-prepare-filter-output alchemist-eval-filter-output)) | |||
| (setq alchemist-eval-filter-output nil))) | |||
| (defun alchemist-eval-quoted-filter (_process output) | |||
| (setq alchemist-eval-filter-output (cons output alchemist-eval-filter-output)) | |||
| (when (alchemist-server-contains-end-marker-p output) | |||
| (alchemist-interact-create-popup alchemist-eval-buffer-name | |||
| (alchemist-server-prepare-filter-output alchemist-eval-filter-output) | |||
| #'alchemist-eval-mode) | |||
| (setq alchemist-eval-filter-output nil))) | |||
| (defun alchemist-eval-quoted-insert-filter (_process output) | |||
| (setq alchemist-eval-filter-output (cons output alchemist-eval-filter-output)) | |||
| (when (alchemist-server-contains-end-marker-p output) | |||
| (alchemist-interact-insert-as-comment | |||
| (alchemist-server-prepare-filter-output alchemist-eval-filter-output)) | |||
| (setq alchemist-eval-filter-output nil))) | |||
| ;; Public functions | |||
| (defun alchemist-eval-current-line () | |||
| "Evaluate the Elixir code on the current line." | |||
| (interactive) | |||
| (alchemist-eval--expression (thing-at-point 'line))) | |||
| (defun alchemist-eval-print-current-line () | |||
| "Evaluate the Elixir code on the current line and insert the result." | |||
| (interactive) | |||
| (alchemist-eval--expression-and-print (thing-at-point 'line))) | |||
| (defun alchemist-eval-region (beg end) | |||
| "Evaluate the Elixir code on marked region." | |||
| (interactive (list (point) (mark))) | |||
| (unless (and beg end) | |||
| (error "The mark is not set now, so there is no region")) | |||
| (let ((string (buffer-substring-no-properties beg end))) | |||
| (alchemist-eval--expression string))) | |||
| (defun alchemist-eval-print-region (beg end) | |||
| "Evaluate the Elixir code on marked region and insert the result." | |||
| (interactive (list (point) (mark))) | |||
| (unless (and beg end) | |||
| (error "The mark is not set now, so there is no region")) | |||
| (let ((string (buffer-substring-no-properties beg end))) | |||
| (when (> end beg) | |||
| (exchange-point-and-mark)) | |||
| (alchemist-eval--expression-and-print string))) | |||
| (defun alchemist-eval-buffer () | |||
| "Evaluate the Elixir code in the current buffer." | |||
| (interactive) | |||
| (let ((string (buffer-substring-no-properties (point-min) (point-max)))) | |||
| (alchemist-eval--expression string))) | |||
| (defun alchemist-eval-print-buffer () | |||
| "Evaluate the Elixir code in the current buffer and insert the result." | |||
| (interactive) | |||
| (let ((string (buffer-substring-no-properties (point-min) (point-max)))) | |||
| (goto-char (point-max)) | |||
| (alchemist-eval--expression-and-print string))) | |||
| (defun alchemist-eval-quoted-current-line () | |||
| "Get the Elixir code representation of the expression on the current line." | |||
| (interactive) | |||
| (alchemist-eval--quote-expression (thing-at-point 'line))) | |||
| (defun alchemist-eval-print-quoted-current-line () | |||
| "Get the Elixir code representation of the expression on the current line and insert the result." | |||
| (interactive) | |||
| (alchemist-eval--quote-expression-and-print (thing-at-point 'line))) | |||
| (defun alchemist-eval-quoted-region (beg end) | |||
| "Get the Elixir code representation of the expression on marked region." | |||
| (interactive (list (point) (mark))) | |||
| (unless (and beg end) | |||
| (error "The mark is not set now, so there is no region")) | |||
| (let ((string (buffer-substring-no-properties beg end))) | |||
| (alchemist-eval--quote-expression string))) | |||
| (defun alchemist-eval-print-quoted-region (beg end) | |||
| "Get the Elixir code representation of the expression on marked region and insert the result." | |||
| (interactive (list (point) (mark))) | |||
| (unless (and beg end) | |||
| (error "The mark is not set now, so there is no region")) | |||
| (let ((string (buffer-substring-no-properties beg end))) | |||
| (when (> end beg) | |||
| (exchange-point-and-mark)) | |||
| (alchemist-eval--quote-expression-and-print string))) | |||
| (defun alchemist-eval-quoted-buffer () | |||
| "Get the Elixir code representation of the expression in the current buffer." | |||
| (interactive) | |||
| (let ((string (buffer-substring-no-properties (point-min) (point-max)))) | |||
| (alchemist-eval--quote-expression string))) | |||
| (defun alchemist-eval-print-quoted-buffer () | |||
| "Get the Elixir code representation of the expression in the current buffer and insert result." | |||
| (interactive) | |||
| (let ((string (buffer-substring-no-properties (point-min) (point-max)))) | |||
| (alchemist-eval--quote-expression-and-print string))) | |||
| (defun alchemist-eval-close-popup () | |||
| "Quit the evaluation buffer window." | |||
| (interactive) | |||
| (quit-windows-on alchemist-eval-buffer-name)) | |||
| (define-minor-mode alchemist-eval-mode | |||
| "Minor mode for Alchemist Elixir code evaluation. | |||
| \\{alchemist-eval-mode-map}" | |||
| nil | |||
| " Alchemist-Eval" | |||
| alchemist-eval-mode-map) | |||
| (provide 'alchemist-eval) | |||
| ;;; alchemist-eval.el ends here | |||
| @ -0,0 +1,93 @@ | |||
| ;;; alchemist-execute.el --- Elixir's script execution integration | |||
| ;; 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: | |||
| ;; Elixir's script execution integration | |||
| ;;; Code: | |||
| (require 'alchemist-utils) | |||
| (require 'alchemist-test-mode) | |||
| (require 'alchemist-report) | |||
| (defgroup alchemist-execute nil | |||
| "Elixir's script execution integration." | |||
| :prefix "alchemist-execute-" | |||
| :group 'alchemist) | |||
| ;; Variables | |||
| (defcustom alchemist-execute-command "elixir" | |||
| "The shell command for elixir." | |||
| :type 'string | |||
| :group 'alchemist-execute) | |||
| (defvar alchemist-execute-buffer-name "*alchemist elixir*" | |||
| "Name of the elixir output buffer.") | |||
| (defvar alchemist-execute-mode-map | |||
| (let ((map (make-sparse-keymap))) | |||
| (define-key map "q" #'quit-window) | |||
| map)) | |||
| ;; Private functions | |||
| (defun alchemist-execute--file (filename) | |||
| (when (not (file-exists-p filename)) | |||
| (error "The given file doesn't exist")) | |||
| (alchemist-execute (list alchemist-execute-command (expand-file-name filename)))) | |||
| (defun alchemist-execute--read-command (command) | |||
| (read-shell-command "elixir command: " (concat command " "))) | |||
| ;; Public functions | |||
| (defun alchemist-execute-this-buffer () | |||
| "Run the current buffer through elixir." | |||
| (interactive) | |||
| (alchemist-execute--file buffer-file-name)) | |||
| (defun alchemist-execute-file (filename) | |||
| "Run elixir with the given FILENAME." | |||
| (interactive "Felixir: ") | |||
| (alchemist-execute--file (expand-file-name filename))) | |||
| (define-derived-mode alchemist-execute-mode fundamental-mode "Elixir Execute Mode" | |||
| "Major mode for execute Elixir files. | |||
| \\{alchemist-execute-mode-map}" | |||
| (setq buffer-read-only t) | |||
| (setq-local truncate-lines t) | |||
| (setq-local electric-indent-chars nil)) | |||
| (defun alchemist-execute (cmdlist) | |||
| "Run a elixir with CMDLIST." | |||
| (interactive (list (alchemist-execute--read-command alchemist-execute-command))) | |||
| (let ((command (alchemist-utils-build-command cmdlist))) | |||
| (alchemist-report-run command | |||
| "alchemist-execute-report" | |||
| alchemist-execute-buffer-name | |||
| 'alchemist-execute-mode))) | |||
| (provide 'alchemist-execute) | |||
| ;;; alchemist-execute.el ends here | |||
| @ -0,0 +1,57 @@ | |||
| ;;; alchemist-file.el --- Functionality to work with directory content | |||
| ;; 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 to work with directory content. | |||
| ;;; Code: | |||
| (defgroup alchemist-file nil | |||
| "Functionality to work with directory content." | |||
| :prefix "alchemist-file-" | |||
| :group 'alchemist) | |||
| (defun alchemist-file-find-files (root directory) | |||
| "Open DIRECTORY inside ROOT and prompt for a file." | |||
| (let* ((files (alchemist-file-read-dir root directory)) | |||
| (root-name (car (cdr (reverse (split-string root "/"))))) | |||
| (file (completing-read (format "[%s] %s: " root-name directory) files))) | |||
| (find-file (expand-file-name file root)))) | |||
| (defun alchemist-file-read-dir (root directory) | |||
| "Return all files in DIRECTORY and use ROOT as `default-directory'." | |||
| (let ((default-directory root)) | |||
| (-map (lambda (file) (file-relative-name file root)) | |||
| (alchemist-file--files-from directory)))) | |||
| (defun alchemist-file--files-from (directory) | |||
| (--mapcat | |||
| (if (file-directory-p it) | |||
| (unless (or (equal (file-relative-name it directory) "..") | |||
| (equal (file-relative-name it directory) ".")) | |||
| (alchemist-file--files-from it)) | |||
| (list it)) | |||
| (directory-files directory t))) | |||
| (provide 'alchemist-file) | |||
| ;;; alchemist-file.el ends here | |||
| @ -0,0 +1,297 @@ | |||
| ;;; alchemist-goto.el --- Functionality to jump modules and function definitions -*- 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: | |||
| ;; Functionality to jump modules and function definitions | |||
| ;;; Code: | |||
| (require 'cl-lib) | |||
| (require 'etags) | |||
| (require 'dash) | |||
| (require 'alchemist-utils) | |||
| (require 'alchemist-server) | |||
| (require 'alchemist-scope) | |||
| (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 '()) | |||
| (defvar alchemist-goto-filter-output nil) | |||
| (defvar alchemist-goto-callback nil) | |||
| (defconst alchemist-goto--symbol-def-extract-regex | |||
| "^\\s-*\\(defp?\\|defmacrop?\\|defmodule\\|defimpl\\)[ \n\t]+\\([a-z_\?!]+\\)\\(.*\\)\\(do\\|\n\\)?$") | |||
| (defconst alchemist-goto--symbol-def-regex | |||
| "^[[:space:]]*\\(defmodule\\|defmacrop?\\|defimpl\\|defp?\\)") | |||
| ;; Faces | |||
| (defface alchemist-goto--def-face | |||
| '((t (:inherit font-lock-constant-face))) | |||
| "Face for def* symbols." | |||
| :group 'alchemist-goto) | |||
| (defface alchemist-goto--name-face | |||
| '((t (:bold t))) | |||
| "Face for def* symbol names." | |||
| :group 'alchemist-goto) | |||
| ;; Private functions | |||
| (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 (alchemist-utils-add-trailing-slash | |||
| (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 (alchemist-utils-add-trailing-slash | |||
| (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 (alchemist-utils-add-trailing-slash | |||
| (expand-file-name alchemist-goto-erlang-source-dir)))) | |||
| (concat source-directory file)))) | |||
| (defun alchemist-goto-elixir-file-p (file) | |||
| "Return non-nil if FILE is an Elixir file type." | |||
| (string-match-p "\\.ex\\(s\\)?$" file)) | |||
| (defun alchemist-goto-erlang-file-p (file) | |||
| "Return non-nil if FILE is an Erlang file type." | |||
| (string-match-p "\\.erl$" file)) | |||
| (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--fetch-symbols-from-propertize-list (symbol) | |||
| (-remove 'null (-map (lambda (e) | |||
| (when (string-match-p (format "^\\s-*\\(defp?\\|defmacrop?\\|defimpl\\|defmodule\\)\s+%s\\((.*\\)?$" symbol) e) | |||
| e)) alchemist-goto--symbol-list))) | |||
| (defun alchemist-goto--goto-symbol (symbol) | |||
| (let ((amount (length (-remove 'null (-map (lambda (e) (when (string= symbol e) e)) | |||
| alchemist-goto--symbol-list-bare))))) | |||
| (if (> amount 1) | |||
| (let* ((selected-def (completing-read "Symbol definitions:" | |||
| (alchemist-goto--fetch-symbols-from-propertize-list symbol))) | |||
| (position (cdr (assoc selected-def alchemist-goto--symbol-name-and-pos)))) | |||
| (goto-char (if (overlayp position) (overlay-start position) position))) | |||
| (let* ((position (cdr (assoc symbol alchemist-goto--symbol-name-and-pos-bare))) | |||
| (position (if (overlayp position) (overlay-start position) position))) | |||
| (when (not (equal (line-number-at-pos) | |||
| (line-number-at-pos position))) | |||
| (goto-char 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?\\|defimpl\\|defstruct\\|defmodule\\)\s.*")) | |||
| (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--file-contains-defs-p () | |||
| (alchemist-utils-occur-in-buffer-p (current-buffer) alchemist-goto--symbol-def-extract-regex)) | |||
| (defun alchemist-goto-jump-to-next-def-symbol () | |||
| (interactive) | |||
| (alchemist-utils-jump-to-next-matching-line alchemist-goto--symbol-def-regex 'back-to-indentation)) | |||
| (defun alchemist-goto-jump-to-previous-def-symbol () | |||
| (interactive) | |||
| (alchemist-utils-jump-to-previous-matching-line alchemist-goto--symbol-def-regex 'back-to-indentation)) | |||
| (defun alchemist-goto--extract-symbol-bare (str) | |||
| (save-match-data | |||
| (when (string-match alchemist-goto--symbol-def-extract-regex str) | |||
| (let ((name (substring str (match-beginning 2) (match-end 2)))) | |||
| name)))) | |||
| (defun alchemist-goto--get-symbol-from-position (position) | |||
| (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 '()) | |||
| (setq alchemist-goto--symbol-list-bare '()) | |||
| (setq alchemist-goto--symbol-name-and-pos-bare '()) | |||
| (with-current-buffer (buffer-name) | |||
| (save-excursion | |||
| (goto-char (point-min)) | |||
| (save-match-data | |||
| (while (re-search-forward regex nil t) | |||
| (when (not (alchemist-scope-inside-string-p)) | |||
| (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-scope-extract-module expr)) | |||
| (aliases (alchemist-utils-prepare-aliases-for-elixir | |||
| (alchemist-scope-aliases))) | |||
| (function (alchemist-scope-extract-function expr)) | |||
| (modules (alchemist-utils-prepare-modules-for-elixir | |||
| (alchemist-scope-all-modules)))) | |||
| (ring-insert find-tag-marker-ring (point-marker)) | |||
| (cond | |||
| ((and (null module) | |||
| (alchemist-goto--symbol-definition-p function) | |||
| (not (string= (buffer-name) alchemist-help-buffer-name))) | |||
| (alchemist-goto--goto-symbol function)) | |||
| (t | |||
| (setq alchemist-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))))) | |||
| (alchemist-server-goto (format "{ \"%s,%s\", [ context: Elixir, imports: %s, aliases: %s ] }" module function modules aliases) | |||
| #'alchemist-goto-filter))))) | |||
| (defun alchemist-goto--open-file (file module function) | |||
| (let ((buf (find-file-noselect file))) | |||
| (switch-to-buffer buf) | |||
| (goto-char (point-min)) | |||
| (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) | |||
| (cond | |||
| (function | |||
| (alchemist-goto--fetch-symbol-definitions) | |||
| (alchemist-goto--goto-symbol function)) | |||
| (t | |||
| (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-filter (_process output) | |||
| (with-local-quit | |||
| (setq alchemist-goto-filter-output (cons output alchemist-goto-filter-output)) | |||
| (if (alchemist-server-contains-end-marker-p output) | |||
| (let* ((output (alchemist-server-prepare-filter-output alchemist-goto-filter-output)) | |||
| (file output)) | |||
| (setq alchemist-goto-filter-output nil) | |||
| (funcall alchemist-goto-callback file))))) | |||
| ;; Public functions | |||
| (defun alchemist-goto-definition-at-point () | |||
| "Jump to the elixir expression definition at point." | |||
| (interactive) | |||
| (alchemist-goto--open-definition (alchemist-scope-expression))) | |||
| (defalias 'alchemist-goto-jump-back 'pop-tag-mark) | |||
| (provide 'alchemist-goto) | |||
| ;;; alchemist-goto.el ends here | |||
| @ -0,0 +1,266 @@ | |||
| ;;; alchemist-help.el --- Functionality for Elixir documentation lookup -*- lexical-binding: t -*- | |||
| ;; Copyright © 2014-2015 Samuel Tonini | |||
| ;; Author: Samuel Tonini <tonini.samuel@gmail.com | |||
| ;; This file is not part of GNU Emacs. | |||
| ;; This program is free software: you can redistribute it and/or modify | |||
| ;; it under the terms of the GNU General Public License as published by | |||
| ;; the Free Software Foundation, either version 3 of the License, or | |||
| ;; (at your option) any later version. | |||
| ;; This program is distributed in the hope that it will be useful, | |||
| ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| ;; GNU General Public License for more details. | |||
| ;; You should have received a copy of the GNU General Public License | |||
| ;; along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| ;;; Commentary: | |||
| ;; Functionality for Elixir documentation lookup. | |||
| ;;; Code: | |||
| (require 'dash) | |||
| (require 'ansi-color) | |||
| (require 'alchemist-utils) | |||
| (require 'alchemist-project) | |||
| (require 'alchemist-server) | |||
| (require 'alchemist-scope) | |||
| (require 'alchemist-goto) | |||
| (defgroup alchemist-help nil | |||
| "Functionality for Elixir documentation lookup." | |||
| :prefix "alchemist-help-" | |||
| :group 'alchemist) | |||
| (defcustom alchemist-help-buffer-name "*alchemist help*" | |||
| "Name of the Elixir help buffer." | |||
| :type 'string | |||
| :group 'alchemist-help) | |||
| (defvar alchemist-help-search-history '() | |||
| "Storage for the search history.") | |||
| (defvar alchemist-help-current-search-text '() | |||
| "Stores the current search.") | |||
| (defvar alchemist-help-filter-output nil) | |||
| (defface alchemist-help-key-face | |||
| '((t (:inherit font-lock-variable-name-face :bold t :foreground "red"))) | |||
| "Face for the letter keys in the summary." | |||
| :group 'alchemist-help) | |||
| (defun alchemist-help-lookup-doc (search) | |||
| "Lookup Elixir documentation for SEARCH." | |||
| (setq alchemist-help-current-search-text search) | |||
| (setq alchemist-help-filter-output nil) | |||
| (if (not (alchemist-utils-empty-string-p search)) | |||
| (alchemist-server-complete-candidates | |||
| (alchemist-help--completion-server-arguments search) | |||
| #'alchemist-help-complete-filter-output) | |||
| (message "No documentation for [%s] found." search))) | |||
| (defun alchemist-help-no-doc-available-p (string) | |||
| "Return non-nil if STRING contains Elixir no documentation message." | |||
| (or (string-match-p "No documentation for" string) | |||
| (string-match-p "Could not load module" string) | |||
| (string-match-p "it does not have Elixir-style docs" string) | |||
| (alchemist-utils-empty-string-p string))) | |||
| (defun alchemist-help-store-search-in-history () | |||
| "Store the last `alchemist-help-current-search-text' in `alchemist-help-search-history'." | |||
| (unless (memq 'alchemist-help-current-search-text alchemist-help-search-history) | |||
| (add-to-list 'alchemist-help-search-history alchemist-help-current-search-text))) | |||
| (defun alchemist-help-display-doc (content) | |||
| "Initialize the `alchemist-help-buffer-name' and insert CONTENT." | |||
| (let ((default-directory (alchemist-project-root-or-default-dir)) | |||
| (buffer (get-buffer-create alchemist-help-buffer-name))) | |||
| (cond | |||
| ((alchemist-help-no-doc-available-p content) | |||
| (message (format "No documentation for [%s] found." | |||
| alchemist-help-current-search-text))) | |||
| (t | |||
| (alchemist-help-store-search-in-history) | |||
| (with-current-buffer buffer | |||
| (let ((inhibit-read-only t)) | |||
| (goto-char (point-min)) | |||
| (erase-buffer) | |||
| (insert content) | |||
| (goto-char (point-min)) | |||
| (ansi-color-apply-on-region (point-min) (point-max)) | |||
| (alchemist-help-minor-mode))) | |||
| (pop-to-buffer buffer))))) | |||
| (defun alchemist-help--search-at-point () | |||
| "Search through `alchemist-help' with the expression under the cursor" | |||
| (let* ((expr (alchemist-scope-expression))) | |||
| (alchemist-help-lookup-doc (alchemist-help--prepare-search-expr expr)))) | |||
| (defun alchemist-help--search-marked-region (begin end) | |||
| "Run `alchemist-help' with the marked region. | |||
| Argument BEGIN where the mark starts. | |||
| Argument END where the mark ends." | |||
| (let ((expr (buffer-substring-no-properties begin end))) | |||
| (alchemist-help-lookup-doc (alchemist-help--prepare-search-expr expr)))) | |||
| (defun alchemist-help--prepare-search-expr (expr) | |||
| (let* ((module (alchemist-scope-extract-module expr)) | |||
| (module (alchemist-scope-alias-full-path module)) | |||
| (module (if module module "")) | |||
| (function (alchemist-scope-extract-function expr)) | |||
| (function (if function function "")) | |||
| (expr (cond | |||
| ((and (not (alchemist-utils-empty-string-p module)) | |||
| (not (alchemist-utils-empty-string-p function))) | |||
| (format "%s.%s" module function)) | |||
| ((not (alchemist-utils-empty-string-p module)) | |||
| module) | |||
| (t | |||
| expr)))) | |||
| expr)) | |||
| (defun alchemist-help--elixir-modules-to-list (str) | |||
| (let* ((str (replace-regexp-in-string "^Elixir\\." "" str)) | |||
| (modules (split-string str)) | |||
| (modules (delete nil modules)) | |||
| (modules (cl-sort modules 'string-lessp :key 'downcase)) | |||
| (modules (-distinct modules))) | |||
| modules)) | |||
| (defun alchemist-help-minor-mode-key-binding-summary () | |||
| (interactive) | |||
| (message | |||
| (concat "[" (propertize "q" 'face 'alchemist-help-key-face) | |||
| "]-quit [" | |||
| (propertize "e" 'face 'alchemist-help-key-face) | |||
| "]-search-at-point [" | |||
| (propertize "s" 'face 'alchemist-help-key-face) | |||
| "]-search [" | |||
| (propertize "h" 'face 'alchemist-help-key-face) | |||
| "]-history [" | |||
| (propertize "?" 'face 'alchemist-help-key-face) | |||
| "]-keys"))) | |||
| (defun alchemist-help--server-arguments (args) | |||
| (if (and (not (equal major-mode 'alchemist-iex-mode)) | |||
| (not (bound-and-true-p alchemist-help-minor-mode))) | |||
| (let* ((modules (alchemist-utils-prepare-modules-for-elixir | |||
| (alchemist-scope-all-modules)))) | |||
| (format "{ \"%s\", [ context: Elixir, imports: %s, aliases: [] ] }" args modules)) | |||
| (format "{ \"%s\", [ context: Elixir, imports: [], aliases: [] ] }" args))) | |||
| (defun alchemist-help--completion-server-arguments (args) | |||
| "Build informations about the current context." | |||
| (if (and (not (equal major-mode 'alchemist-iex-mode)) | |||
| (not (bound-and-true-p alchemist-help-minor-mode))) | |||
| (let* ((modules (alchemist-utils-prepare-modules-for-elixir | |||
| (alchemist-scope-all-modules))) | |||
| (aliases (alchemist-utils-prepare-aliases-for-elixir | |||
| (alchemist-scope-aliases)))) | |||
| (format "{ \"%s\", [ context: Elixir, imports: %s, aliases: %s ] }" args modules aliases)) | |||
| (format "{ \"%s\", [ context: Elixir, imports: [], aliases: [] ] }" args))) | |||
| (defun alchemist-help-complete-filter-output (_process output) | |||
| (with-local-quit | |||
| (setq alchemist-help-filter-output (cons output alchemist-help-filter-output)) | |||
| (if (alchemist-server-contains-end-marker-p output) | |||
| (let* ((string (alchemist-server-prepare-filter-output alchemist-help-filter-output)) | |||
| (candidates (alchemist-complete--output-to-list | |||
| (ansi-color-filter-apply string))) | |||
| (candidates (if (= (length candidates) 2) | |||
| nil | |||
| candidates))) | |||
| (setq alchemist-help-filter-output nil) | |||
| (if candidates | |||
| (let* ((search (alchemist-complete--completing-prompt alchemist-help-current-search-text candidates))) | |||
| (setq alchemist-help-current-search-text search) | |||
| (alchemist-server-help (alchemist-help--server-arguments search) #'alchemist-help-filter-output)) | |||
| (alchemist-server-help (alchemist-help--server-arguments alchemist-help-current-search-text) #'alchemist-help-filter-output)))))) | |||
| (defun alchemist-help-filter-output (_process output) | |||
| (setq alchemist-help-filter-output (cons output alchemist-help-filter-output)) | |||
| (if (alchemist-server-contains-end-marker-p output) | |||
| (let ((string (alchemist-server-prepare-filter-output alchemist-help-filter-output))) | |||
| (alchemist-help-display-doc string) | |||
| (setq alchemist-help-current-search-text nil) | |||
| (setq alchemist-help-filter-output nil)))) | |||
| (defun alchemist-help-modules-filter (_process output) | |||
| (with-local-quit | |||
| (setq alchemist-help-filter-output (cons output alchemist-help-filter-output)) | |||
| (if (alchemist-server-contains-end-marker-p output) | |||
| (let* ((output (alchemist-server-prepare-filter-output alchemist-help-filter-output)) | |||
| (modules (alchemist-help--elixir-modules-to-list output)) | |||
| (search (completing-read | |||
| "Elixir help: " | |||
| modules | |||
| nil | |||
| nil | |||
| nil)) | |||
| (module (alchemist-scope-extract-module search)) | |||
| (function (alchemist-scope-extract-function search)) | |||
| (search (cond | |||
| ((and module function) | |||
| search) | |||
| ((and module | |||
| (not (string-match-p "[\/0-9]+$" module))) | |||
| (concat module ".")) | |||
| (t | |||
| search)))) | |||
| (alchemist-help-lookup-doc (alchemist-utils-remove-dot-at-the-end search)))))) | |||
| ;; Public functions | |||
| (defun alchemist-help-search-at-point () | |||
| "Search through `alchemist-help' with the expression under the cursor. | |||
| If the buffer local variable `mark-active' is non-nil, | |||
| the actively marked region will be used for passing to `alchemist-help'." | |||
| (interactive) | |||
| (if mark-active | |||
| (alchemist-help--search-marked-region (region-beginning) (region-end)) | |||
| (alchemist-help--search-at-point))) | |||
| (defvar alchemist-help-minor-mode-map | |||
| (let ((map (make-sparse-keymap))) | |||
| (define-key map (kbd "q") #'quit-window) | |||
| (define-key map (kbd "e") #'alchemist-help-search-at-point) | |||
| (define-key map (kbd "s") #'alchemist-help) | |||
| (define-key map (kbd "h") #'alchemist-help-history) | |||
| (define-key map (kbd "M-.") #'alchemist-goto-definition-at-point) | |||
| (define-key map (kbd "?") #'alchemist-help-minor-mode-key-binding-summary) | |||
| map) | |||
| "Keymap for `alchemist-help-minor-mode'.") | |||
| (define-minor-mode alchemist-help-minor-mode | |||
| "Minor mode for displaying elixir help." | |||
| :group 'alchemist-help | |||
| :keymap alchemist-help-minor-mode-map | |||
| (cond (alchemist-help-minor-mode | |||
| (setq buffer-read-only t)) | |||
| (t | |||
| (setq buffer-read-only nil)))) | |||
| (defun alchemist-help () | |||
| "Load Elixir documentation for SEARCH." | |||
| (interactive) | |||
| (setq alchemist-help-filter-output nil) | |||
| (alchemist-server-info "{ :type, :modules }" #'alchemist-help-modules-filter)) | |||
| (defun alchemist-help-history (search) | |||
| "Load Elixir from the documentation history for SEARCH." | |||
| (interactive | |||
| (list | |||
| (completing-read "Elixir help history: " alchemist-help-search-history nil nil ""))) | |||
| (alchemist-help-lookup-doc search)) | |||
| (provide 'alchemist-help) | |||
| ;;; alchemist-help.el ends here | |||
| @ -0,0 +1,60 @@ | |||
| ;;; alchemist-hooks.el --- Hooks functionality | |||
| ;; Copyright © 2014-2015 Samuel Tonini | |||
| ;; Author: Samuel Tonini <tonini.samuel@gmail.com | |||
| ;; Dave Thomas <http://pragdave.me> | |||
| ;; 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: | |||
| ;; Hooks functionality | |||
| ;;; Code: | |||
| (require 'alchemist-project) | |||
| (require 'alchemist-mix) | |||
| (require 'alchemist-report) | |||
| (require 'alchemist-test-mode) | |||
| (defgroup alchemist-hooks nil | |||
| "Hooks" | |||
| :prefix "alchemist-hooks-" | |||
| :group 'alchemist) | |||
| (defcustom alchemist-hooks-test-on-save nil | |||
| "If t, run `alchemist-mix-test' on save." | |||
| :type 'boolean | |||
| :group 'alchemist-hooks) | |||
| (defun alchemist-hooks-test-on-save () | |||
| (when (and alchemist-hooks-test-on-save | |||
| (alchemist-project-p)) | |||
| (alchemist-report-run "mix test" | |||
| alchemist-test-report-process-name | |||
| alchemist-test-report-buffer-name | |||
| #'alchemist-test-report-mode | |||
| #'alchemist-test--handle-exit | |||
| t))) | |||
| (eval-after-load 'elixir-mode | |||
| '(progn | |||
| (add-hook 'after-save-hook 'alchemist-hooks-test-on-save nil nil))) | |||
| (provide 'alchemist-hooks) | |||
| ;;; alchemist-hooks.el ends here | |||
| @ -0,0 +1,229 @@ | |||
| ;;; alchemist-help.el --- Interaction with an Elixir IEx process | |||
| ;; 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: | |||
| ;; Interaction with an Elixir IEx process | |||
| ;;; Code: | |||
| (require 'comint) | |||
| (require 'company) | |||
| (require 'alchemist-key) | |||
| (require 'alchemist-scope) | |||
| (require 'alchemist-project) | |||
| (defgroup alchemist-iex nil | |||
| "Interaction with an Elixir IEx process." | |||
| :prefix "alchemist-iex-" | |||
| :group 'alchemist) | |||
| (defcustom alchemist-iex-program-name "iex" | |||
| "The shell command for iex." | |||
| :type 'string | |||
| :group 'alchemist-iex) | |||
| (defcustom alchemist-iex-prompt-read-only t | |||
| "If non-nil, the prompt will be read-only." | |||
| :type 'boolean | |||
| :group 'alchemist-iex) | |||
| (defvar alchemist-iex-buffer nil | |||
| "The buffer in which the Elixir IEx process is running.") | |||
| (defvar alchemist-iex-mode-hook nil | |||
| "Hook for customizing `alchemist-iex-mode'.") | |||
| (defvar alchemist-iex-mode-map | |||
| (let ((map (nconc (make-sparse-keymap) comint-mode-map))) | |||
| (define-key map "\t" 'completion-at-point) | |||
| (define-key map (kbd (format "%s i r" alchemist-key-command-prefix)) 'alchemist-iex-open-input-ring) | |||
| (define-key map (kbd (format "%s i c" alchemist-key-command-prefix)) 'alchemist-iex-clear-buffer) | |||
| (define-key map (kbd (format "%s h e" alchemist-key-command-prefix)) 'alchemist-help-search-at-point) | |||
| (define-key map (kbd "M-.") 'alchemist-goto-definition-at-point) | |||
| map)) | |||
| (eval-after-load 'company | |||
| '(progn | |||
| (defun alchemist-iex--set-company-as-completion-at-point-function () | |||
| (setq completion-at-point-functions '(company-complete))) | |||
| (add-hook 'alchemist-iex-mode-hook 'alchemist-iex--set-company-as-completion-at-point-function))) | |||
| (define-derived-mode alchemist-iex-mode comint-mode "Alchemist-IEx" | |||
| "Major mode for interacting with an Elixir IEx process. | |||
| \\<alchemist-iex-mode-map>" | |||
| nil "Alchemist-IEx" | |||
| (set (make-local-variable 'comint-prompt-regexp) "^\\(iex\\|\.\.\.\\)\(.+\)>") | |||
| (set (make-local-variable 'comint-prompt-read-only) alchemist-iex-prompt-read-only) | |||
| (set (make-local-variable 'comint-input-autoexpand) nil) | |||
| (set (make-local-variable 'comint-input-sender) 'alchemist-iex--send-command) | |||
| (add-hook 'comint-output-filter-functions 'alchemist-iex-spot-prompt nil t)) | |||
| (defun alchemist-iex-command (arg) | |||
| (split-string-and-unquote | |||
| (if (null arg) alchemist-iex-program-name | |||
| (read-string "Command to run Elixir IEx: " (concat alchemist-iex-program-name arg))))) | |||
| (defun alchemist-iex-start-process (command) | |||
| "Start an IEX process. | |||
| With universal prefix \\[universal-argument], prompts for a COMMAND, | |||
| otherwise uses `alchemist-iex-program-name'. | |||
| It runs the hook `alchemist-iex-mode-hook' after starting the process and | |||
| setting up the IEx buffer." | |||
| (interactive (list (alchemist-iex-command current-prefix-arg))) | |||
| (setq alchemist-iex-buffer | |||
| (apply 'make-comint "Alchemist-IEx" (car command) nil (cdr command))) | |||
| (with-current-buffer alchemist-iex-buffer | |||
| (alchemist-iex-mode) | |||
| (run-hooks 'alchemist-iex-mode-hook))) | |||
| (defun alchemist-iex-process (&optional arg) | |||
| (or (if (buffer-live-p alchemist-iex-buffer) | |||
| (get-buffer-process alchemist-iex-buffer)) | |||
| (progn | |||
| (let ((current-prefix-arg arg)) | |||
| (call-interactively 'alchemist-iex-start-process)) | |||
| (alchemist-iex-process arg)))) | |||
| (defun alchemist-iex--remove-newlines (string) | |||
| (replace-regexp-in-string "\n" " " string)) | |||
| (defun alchemist-iex-send-last-sexp () | |||
| "Send the previous sexp to the inferior IEx process." | |||
| (interactive) | |||
| (alchemist-iex-send-region (save-excursion (backward-sexp) (point)) (point))) | |||
| (defun alchemist-iex-send-current-line () | |||
| "Sends the current line to the IEx process." | |||
| (interactive) | |||
| (let ((str (thing-at-point 'line))) | |||
| (alchemist-iex--send-command (alchemist-iex-process) str))) | |||
| (defun alchemist-iex-send-current-line-and-go () | |||
| "Sends the current line to the inferior IEx process | |||
| and jump to the buffer." | |||
| (interactive) | |||
| (call-interactively 'alchemist-iex-send-current-line) | |||
| (pop-to-buffer (process-buffer (alchemist-iex-process)))) | |||
| (defun alchemist-iex-send-region-and-go () | |||
| "Sends the marked region to the inferior IEx process | |||
| and jump to the buffer." | |||
| (interactive) | |||
| (call-interactively 'alchemist-iex-send-region) | |||
| (pop-to-buffer (process-buffer (alchemist-iex-process)))) | |||
| (defun alchemist-iex-send-region (beg end) | |||
| "Sends the marked region to the IEx process." | |||
| (interactive (list (point) (mark))) | |||
| (unless (and beg end) | |||
| (error "The mark is not set now, so there is no region")) | |||
| (let* ((region (buffer-substring-no-properties beg end))) | |||
| (alchemist-iex--send-command (alchemist-iex-process) region))) | |||
| (defun alchemist-iex-compile-this-buffer () | |||
| "Compiles the current buffer in the IEx process." | |||
| (interactive) | |||
| (let ((str (format "c(\"%s\")" (buffer-file-name)))) | |||
| (alchemist-iex--send-command (alchemist-iex-process) str))) | |||
| (defun alchemist-iex-reload-module () | |||
| "Recompiles and reloads the current module in the IEx process." | |||
| (interactive) | |||
| (let ((str (format "r(%s)" (alchemist-scope-module)))) | |||
| (alchemist-iex--send-command (alchemist-iex-process) str))) | |||
| (defun alchemist-iex--send-command (proc str) | |||
| (let ((lines (split-string str "\n" nil))) | |||
| (with-current-buffer (process-buffer proc) | |||
| (-map (lambda (line) | |||
| (alchemist-iex-wait-for-prompt proc) | |||
| (goto-char (process-mark proc)) | |||
| (insert-before-markers (concat line "\n")) | |||
| (move-marker comint-last-input-end (point)) | |||
| (comint-send-string proc (concat line "\n"))) lines)))) | |||
| (defvar alchemist-iex-seen-prompt nil) | |||
| (make-variable-buffer-local 'alchemist-iex-seen-prompt) | |||
| (defun alchemist-iex-spot-prompt (_string) | |||
| (let ((proc (get-buffer-process (current-buffer)))) | |||
| (when proc | |||
| (save-excursion | |||
| (goto-char (process-mark proc)) | |||
| (if (re-search-backward comint-prompt-regexp | |||
| (line-beginning-position) t) | |||
| (setq alchemist-iex-seen-prompt t)))))) | |||
| (defun alchemist-iex-wait-for-prompt (proc &optional timeout) | |||
| "Wait until PROC sends us a prompt. | |||
| The process PROC should be associated to a comint buffer." | |||
| (with-current-buffer (process-buffer proc) | |||
| (while (progn | |||
| (goto-char comint-last-input-end) | |||
| (not (or alchemist-iex-seen-prompt | |||
| (setq alchemist-iex-seen-prompt | |||
| (re-search-forward comint-prompt-regexp nil t)) | |||
| (not (accept-process-output proc timeout)))))) | |||
| (unless alchemist-iex-seen-prompt | |||
| (error "Can't find the IEx prompt")) | |||
| (setq alchemist-iex-seen-prompt nil))) | |||
| (defun alchemist-iex-clear-buffer () | |||
| "Clear the current iex process buffer." | |||
| (interactive) | |||
| (let ((comint-buffer-maximum-size 0)) | |||
| (comint-truncate-buffer))) | |||
| (defun alchemist-iex-open-input-ring () | |||
| "Open the buffer containing the input history." | |||
| (interactive) | |||
| (progn | |||
| (comint-dynamic-list-input-ring) | |||
| (other-window 1))) | |||
| ;;;###autoload | |||
| (defalias 'run-elixir 'alchemist-iex-run) | |||
| (defalias 'inferior-elixir 'alchemist-iex-run) | |||
| ;;;###autoload | |||
| (defun alchemist-iex-run (&optional arg) | |||
| "Start an IEx process. | |||
| Show the IEx buffer if an IEx process is already run." | |||
| (interactive "P") | |||
| (let ((proc (alchemist-iex-process arg))) | |||
| (pop-to-buffer (process-buffer proc)))) | |||
| ;;;###autoload | |||
| (defun alchemist-iex-project-run () | |||
| "Start an IEx process with mix 'iex -S mix' in the | |||
| context of an Elixir project. | |||
| Show the IEx buffer if an IEx process is already run." | |||
| (interactive) | |||
| (if (alchemist-project-p) | |||
| (let ((default-directory (alchemist-project-root))) | |||
| (pop-to-buffer (process-buffer (alchemist-iex-process " -S mix")))) | |||
| (message "No mix.exs file available. Please use `alchemist-iex-run' instead."))) | |||
| (provide 'alchemist-iex) | |||
| ;;; alchemist-iex.el ends here | |||
| @ -0,0 +1,95 @@ | |||
| ;;; alchemist-info.el --- Getting informations from the server. -*- 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: | |||
| ;; Getting informations from the server. | |||
| ;;; Code: | |||
| (require 'ansi-color) | |||
| (defgroup alchemist-info nil | |||
| "Getting informations from the server." | |||
| :prefix "alchemist-info-" | |||
| :group 'alchemist) | |||
| (defconst alchemist-info-buffer-name "*alchemist-info-mode*" | |||
| "Name of the Elixir info buffer.") | |||
| (defvar alchemist-info-filter-output nil) | |||
| (defvar alchemist-info-mode-map | |||
| (let ((map (make-sparse-keymap))) | |||
| (define-key map (kbd "q") #'quit-window) | |||
| map) | |||
| "Keymap for `alchemist-info-mode'.") | |||
| (defun alchemist-info-datatype-filter (_process output) | |||
| (setq alchemist-info-filter-output (cons output alchemist-info-filter-output)) | |||
| (when (alchemist-server-contains-end-marker-p output) | |||
| (alchemist-interact-create-popup alchemist-info-buffer-name | |||
| (alchemist-server-prepare-filter-output alchemist-info-filter-output) | |||
| #'(lambda () | |||
| (alchemist-info-mode) | |||
| (ansi-color-apply-on-region (point-min) (point-max)))) | |||
| (setq alchemist-info-filter-output nil))) | |||
| (defun alchemist-info-expression-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-info-datatype-at-point () | |||
| "Return information about any datatype under the cursor." | |||
| (interactive) | |||
| (let ((expr (if mark-active | |||
| (buffer-substring-no-properties (region-beginning) (region-end)) | |||
| (alchemist-info-expression-at-point)))) | |||
| (alchemist-server-info (format "{ :type, :info, '%s' }" expr) #'alchemist-info-datatype-filter))) | |||
| (defun alchemist-info-types-at-point () | |||
| "Return information of types under the cursor." | |||
| (interactive) | |||
| (let ((expr (alchemist-info-expression-at-point))) | |||
| (alchemist-server-info (format "{ :type, :types, '%s' }" expr) #'alchemist-info-datatype-filter))) | |||
| (defun alchemist-info-close-popup () | |||
| "Quit the information buffer window." | |||
| (interactive) | |||
| (quit-windows-on alchemist-info-buffer-name)) | |||
| (define-minor-mode alchemist-info-mode | |||
| "Minor mode for Alchemist server information. | |||
| \\{alchemist-info-mode-map}" | |||
| nil | |||
| " Alchemist-Info" | |||
| alchemist-info-mode-map) | |||
| (provide 'alchemist-info) | |||
| ;;; alchemist-info.el ends here | |||
| @ -0,0 +1,64 @@ | |||
| ;;; alchemist-interact.el --- Interaction interface -*- 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: | |||
| ;; Interaction interface. | |||
| ;;; Code: | |||
| (defgroup alchemist-interact nil | |||
| "Interaction interface." | |||
| :prefix "alchemist-interact-" | |||
| :group 'alchemist) | |||
| (defun alchemist-interact-insert-as-comment (string) | |||
| "Insert STRING at point as comment." | |||
| (let ((lines (split-string string "\n"))) | |||
| (if (> (length lines) 1) | |||
| (save-excursion | |||
| (end-of-line) | |||
| (mapc (lambda (s) | |||
| (newline) | |||
| (insert (format "# => %s" s)) | |||
| (indent-according-to-mode)) | |||
| lines)) | |||
| (save-excursion | |||
| (end-of-line) | |||
| (insert (format " # => %s" string)))))) | |||
| (defun alchemist-interact-create-popup (name content mode) | |||
| "Create a NAME buffer and insert CONTENT. | |||
| Call the MODE afterwards." | |||
| (let ((buffer (get-buffer-create name))) | |||
| (with-current-buffer buffer | |||
| (with-current-buffer-window | |||
| buffer (cons 'display-buffer-below-selected | |||
| '((window-height . fit-window-to-buffer))) | |||
| (lambda (_window _buffer)) | |||
| (let ((inhibit-read-only t)) | |||
| (insert content) | |||
| (goto-char (point-min)) | |||
| (funcall mode)))))) | |||
| (provide 'alchemist-interact) | |||
| ;;; alchemist-interact.el ends here | |||
| @ -0,0 +1,40 @@ | |||
| ;;; alchemist-key.el --- Key prefix setup for Alchemist related key commands | |||
| ;; 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: | |||
| ;; Key prefix setup for Alchemist related key commands. | |||
| ;;; Code: | |||
| (defgroup alchemist-key nil | |||
| "Key prefix setup for Alchemist related key commands." | |||
| :prefix "alchemist-key-" | |||
| :group 'alchemist) | |||
| (defcustom alchemist-key-command-prefix (kbd "C-c a") | |||
| "The prefix for Alchemist related key commands." | |||
| :type 'string | |||
| :group 'alchemist) | |||
| (provide 'alchemist-key) | |||
| ;;; alchemist-key.el ends here | |||
| @ -0,0 +1,156 @@ | |||
| ;;; alchemist-macroexpand.el --- Macro expansion support -*- 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: | |||
| ;; Macro expansion support | |||
| ;;; Code: | |||
| (require 'alchemist-server) | |||
| (require 'alchemist-interact) | |||
| (defgroup alchemist-macroexpand nil | |||
| "Macro expansion support." | |||
| :prefix "alchemist-macroexpand-" | |||
| :group 'alchemist) | |||
| (defvar alchemist-macroexpand-filter-output nil) | |||
| (defconst alchemist-macroexpand-buffer-name "*alchemist macroexpand*" | |||
| "Name of the Elixir Macro expand buffer.") | |||
| (defun alchemist-macroexpand-filter (_process output) | |||
| (setq alchemist-macroexpand-filter-output (cons output alchemist-macroexpand-filter-output)) | |||
| (when (alchemist-server-contains-end-marker-p output) | |||
| (alchemist-interact-create-popup alchemist-macroexpand-buffer-name | |||
| (alchemist-server-prepare-filter-output alchemist-macroexpand-filter-output) | |||
| #'(lambda () | |||
| (elixir-mode) | |||
| (alchemist-macroexpand-mode))) | |||
| (setq alchemist-macroexpand-filter-output nil))) | |||
| (defun alchemist-macroexpand-insert-filter (_process output) | |||
| (setq alchemist-macroexpand-filter-output (cons output alchemist-macroexpand-filter-output)) | |||
| (when (alchemist-server-contains-end-marker-p output) | |||
| (alchemist-interact-insert-as-comment | |||
| (alchemist-server-prepare-filter-output alchemist-macroexpand-filter-output)) | |||
| (setq alchemist-macroexpand-filter-output nil))) | |||
| (defun alchemist-macroexpand-expand-request (expr) | |||
| (let ((file (make-temp-file "alchemist-expand" nil ".exs"))) | |||
| (with-temp-file file | |||
| (insert expr)) | |||
| (alchemist-server-eval (format "{ :expand, '%s' }" file) #'alchemist-macroexpand-filter))) | |||
| (defun alchemist-macroexpand-expand-and-print-request (expr) | |||
| (let ((file (make-temp-file "alchemist-expand" nil ".exs"))) | |||
| (with-temp-file file | |||
| (insert expr)) | |||
| (alchemist-server-eval (format "{ :expand, '%s' }" file) #'alchemist-macroexpand-insert-filter))) | |||
| (defun alchemist-macroexpand-expand-once-request (expr) | |||
| (let ((file (make-temp-file "alchemist-expand-once" nil ".exs"))) | |||
| (with-temp-file file | |||
| (insert expr)) | |||
| (alchemist-server-eval (format "{ :expand_once, '%s' }" file) #'alchemist-macroexpand-filter))) | |||
| (defun alchemist-macroexpand-expand-once-and-print-request (expr) | |||
| (let ((file (make-temp-file "alchemist-expand-once" nil ".exs"))) | |||
| (with-temp-file file | |||
| (insert expr)) | |||
| (alchemist-server-eval (format "{ :expand_once, '%s' }" file) #'alchemist-macroexpand-insert-filter))) | |||
| (defun alchemist-macroexpand-current-line () | |||
| "Macro expand the Elixir code on the current line." | |||
| (interactive) | |||
| (alchemist-macroexpand-expand-request (thing-at-point 'line))) | |||
| (defun alchemist-macroexpand-print-current-line () | |||
| "Macro expand the Elixir code on the current line and insert the result." | |||
| (interactive) | |||
| (alchemist-macroexpand-expand-and-print-request (thing-at-point 'line))) | |||
| (defun alchemist-macroexpand-once-current-line () | |||
| "Macro expand the Elixir code on the current line." | |||
| (interactive) | |||
| (alchemist-macroexpand-expand-once-request (thing-at-point 'line))) | |||
| (defun alchemist-macroexpand-once-print-current-line () | |||
| "Macro expand the Elixir code on the current line and insert the result." | |||
| (interactive) | |||
| (alchemist-macroexpand-expand-once-and-print-request (thing-at-point 'line))) | |||
| (defun alchemist-macroexpand-print-region (beg end) | |||
| "Macro expand the Elixir code on marked region and insert the result." | |||
| (interactive (list (point) (mark))) | |||
| (unless (and beg end) | |||
| (error "The mark is not set now, so there is no region")) | |||
| (let ((string (buffer-substring-no-properties beg end))) | |||
| (when (> end beg) | |||
| (exchange-point-and-mark)) | |||
| (alchemist-macroexpand-expand-and-print-request string))) | |||
| (defun alchemist-macroexpand-region (beg end) | |||
| "Macro expand the Elixir code on marked region." | |||
| (interactive (list (point) (mark))) | |||
| (unless (and beg end) | |||
| (error "The mark is not set now, so there is no region")) | |||
| (let ((string (buffer-substring-no-properties beg end))) | |||
| (alchemist-macroexpand-expand-request string))) | |||
| (defun alchemist-macroexpand-once-print-region (beg end) | |||
| "Macro expand the Elixir code on marked region once and insert the result." | |||
| (interactive "r") | |||
| (let ((string (buffer-substring-no-properties beg end))) | |||
| (when (> end beg) | |||
| (exchange-point-and-mark)) | |||
| (alchemist-macroexpand-expand-once-and-print-request string))) | |||
| (defun alchemist-macroexpand-once-region (beg end) | |||
| "Macro expand the Elixir code on marked region once." | |||
| (interactive (list (point) (mark))) | |||
| (unless (and beg end) | |||
| (error "The mark is not set now, so there is no region")) | |||
| (let ((string (buffer-substring-no-properties beg end))) | |||
| (alchemist-macroexpand-expand-once-request string))) | |||
| (defun alchemist-macroexpand-close-popup () | |||
| "Quit the macroexpand buffer window." | |||
| (interactive) | |||
| (quit-windows-on alchemist-macroexpand-buffer-name)) | |||
| (defvar alchemist-macroexpand-mode-map | |||
| (let ((map (make-sparse-keymap))) | |||
| (define-key map (kbd "q") #'quit-window) | |||
| map) | |||
| "Keymap for `alchemist-macroexpand-mode'.") | |||
| (define-minor-mode alchemist-macroexpand-mode | |||
| "Minor mode for Alchemist Elixir macroexpand functionality. | |||
| \\{alchemist-macroexpand-mode-map}" | |||
| nil | |||
| alchemist-macroexpand-mode-map) | |||
| (provide 'alchemist-macroexpand) | |||
| ;;; alchemist-macroexpand.el ends here | |||
| @ -0,0 +1,227 @@ | |||
| ;;; alchemist-mix.el --- Interface to run Elixir mix tasks inside Emacs | |||
| ;; 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: | |||
| ;; Interface to run Elixir mix tasks inside Emacs. | |||
| ;;; Code: | |||
| (require 'alchemist-utils) | |||
| (require 'alchemist-project) | |||
| (require 'alchemist-test-mode) | |||
| (require 'alchemist-server) | |||
| (defgroup alchemist-mix nil | |||
| "Emacs integration for Elixir's mix." | |||
| :prefix "alchemist-mix-" | |||
| :group 'alchemist) | |||
| ;; Variables | |||
| (defvar alchemist-last-run-test nil) | |||
| (defvar alchemist-mix-filter-output nil) | |||
| (defcustom alchemist-mix-command "mix" | |||
| "The shell command for mix." | |||
| :type 'string | |||
| :group 'alchemist-mix) | |||
| (defcustom alchemist-mix-test-task "test" | |||
| "Default task to run tests." | |||
| :type 'string | |||
| :group 'alchemist-mix) | |||
| (defcustom alchemist-mix-test-default-options '() | |||
| "Default options for alchemist test command." | |||
| :type '(repeat string) | |||
| :group 'alchemist-mix) | |||
| (defcustom alchemist-mix-env nil | |||
| "The default mix env to run mix commands with. If nil, the mix env is | |||
| not set explicitly." | |||
| :type '(string boolean) | |||
| :group 'alchemist-mix) | |||
| (defvar alchemist-mix-mode-map | |||
| (let ((map (make-sparse-keymap))) | |||
| (define-key map "q" #'quit-window) | |||
| (define-key map "i" #'alchemist-mix-send-input-to-mix-process) | |||
| map)) | |||
| (defvar alchemist-mix-buffer-name "*alchemist mix*" | |||
| "Name of the mix output buffer.") | |||
| (defvar alchemist-mix--envs '("dev" "prod" "test") | |||
| "The list of mix envs to use as defaults.") | |||
| ;; Private functions | |||
| (defun alchemist-mix--completing-read (prompt cmdlist) | |||
| (completing-read prompt cmdlist nil t nil nil (car cmdlist))) | |||
| (defun alchemist-mix--execute-test (&optional what) | |||
| "Execute 'mix test' on the given `WHAT'. | |||
| `WHAT' could be a filename, a filename:line string or the empty string (meaning | |||
| run all tests)." | |||
| (if what | |||
| (setq alchemist-last-run-test what) | |||
| (setq alchemist-last-run-test "")) | |||
| (alchemist-test-execute (list "mix" | |||
| alchemist-mix-test-task | |||
| what | |||
| alchemist-mix-test-default-options))) | |||
| (defun alchemist-mix--test-file (filename) | |||
| "Run a specific FILENAME as argument for the mix command test." | |||
| (when (not (file-exists-p filename)) | |||
| (error "The given file doesn't exist")) | |||
| (alchemist-mix--execute-test (expand-file-name filename))) | |||
| ;; Public functions | |||
| (defun alchemist-mix () | |||
| "Prompt for a specific mix task to run. | |||
| If the command `universal-argument' is called before `alchemist-mix', | |||
| a prompt for a specific mix environment in which the task will be | |||
| executed, gets called." | |||
| (interactive) | |||
| (alchemist-server-info "{ :type, :mixtasks }" #'alchemist-mix-filter)) | |||
| (defun alchemist-mix-display-mix-buffer () | |||
| "Display the mix buffer when exists." | |||
| (interactive) | |||
| (when (get-buffer alchemist-mix-buffer-name) | |||
| (display-buffer alchemist-mix-buffer-name))) | |||
| (defun alchemist-mix-test () | |||
| "Run the whole elixir test suite." | |||
| (interactive) | |||
| (alchemist-mix--execute-test)) | |||
| (defun alchemist-mix-test-this-buffer () | |||
| "Run the current buffer through mix test." | |||
| (interactive) | |||
| (alchemist-mix--test-file buffer-file-name)) | |||
| (defun alchemist-mix-test-file (filename) | |||
| "Run `alchemist-mix--test-file' with the FILENAME." | |||
| (interactive "Fmix test: ") | |||
| (alchemist-mix--test-file (expand-file-name filename))) | |||
| (defun alchemist-mix-test-at-point () | |||
| "Run the test at point." | |||
| (interactive) | |||
| (let* ((line (line-number-at-pos (point))) | |||
| (file-and-line (format "%s:%s" buffer-file-name line))) | |||
| (alchemist-mix--execute-test file-and-line))) | |||
| (defun alchemist-mix-rerun-last-test () | |||
| "Rerun the last test that was run by alchemist. | |||
| When no tests had been run before calling this function, do nothing." | |||
| (interactive) | |||
| (if alchemist-last-run-test | |||
| (alchemist-mix--execute-test alchemist-last-run-test) | |||
| (message "No tests have been run yet"))) | |||
| (defun alchemist-mix-compile (command &optional prefix) | |||
| "Compile the whole elixir project. Prompt for the mix env if the prefix | |||
| arg is set." | |||
| (interactive "Mmix compile: \nP") | |||
| (alchemist-mix-execute (list "compile" command) prefix)) | |||
| (defun alchemist-mix-run (command &optional prefix) | |||
| "Runs the given file or expression in the context of the application." | |||
| (interactive "Mmix run: \nP") | |||
| (alchemist-mix-execute (list "run" command) prefix)) | |||
| (defun alchemist-mix-send-input-to-mix-process (input) | |||
| "Send INPUT to the current running mix task process." | |||
| (interactive "MSend to running mix task: ") | |||
| (let* ((buffer (get-buffer alchemist-mix-buffer-name)) | |||
| (process (get-buffer-process buffer))) | |||
| (if (and process (eq (process-status process) 'run)) | |||
| (with-current-buffer buffer | |||
| (let ((inhibit-read-only t)) | |||
| (goto-char (point-max)) | |||
| (insert (concat input "\n\n")) | |||
| (set-marker (process-mark process) (point))) | |||
| (comint-send-string process (concat input "\n"))) | |||
| (error "No %s process is running" alchemist-mix-buffer-name)))) | |||
| (defun alchemist-mix-help () (interactive) | |||
| (alchemist-utils-deprecated-message "alchemist-mix-help" "alchemist-mix")) | |||
| (defun alchemist-mix-new () (interactive) | |||
| (alchemist-utils-deprecated-message "alchemist-mix-new" "alchemist-mix")) | |||
| (defun alchemist-mix-deps () (interactive) | |||
| (alchemist-utils-deprecated-message "alchemist-mix-deps" "alchemist-mix")) | |||
| (defun alchemist-mix-deps-with-prompt () (interactive) | |||
| (alchemist-utils-deprecated-message "alchemist-mix-deps-with-prompt" "alchemist-mix")) | |||
| (defun alchemist-mix-local-with-prompt () (interactive) | |||
| (alchemist-utils-deprecated-message "alchemist-mix-local-with-prompt" "alchemist-mix")) | |||
| (defun alchemist-mix-local () (interactive) | |||
| (alchemist-utils-deprecated-message "alchemist-mix-local" "alchemist-mix")) | |||
| (defun alchemist-mix-local-install () (interactive) | |||
| (alchemist-utils-deprecated-message "alchemist-mix-local-install" "alchemist-mix")) | |||
| (defun alchemist-mix-local-with-url () (interactive) | |||
| (alchemist-utils-deprecated-message "alchemist-mix-local-with-url" "alchemist-mix")) | |||
| (defun alchemist-mix-local-with-path () (interactive) | |||
| (alchemist-utils-deprecated-message "alchemist-mix-local-with-path" "alchemist-mix")) | |||
| (defun alchemist-mix-hex-search () (interactive) | |||
| (alchemist-utils-deprecated-message "alchemist-mix-local-hex-search" "alchemist-mix")) | |||
| (defun alchemist-mix-filter (_process output) | |||
| (with-local-quit | |||
| (setq alchemist-mix-filter-output (cons output alchemist-mix-filter-output)) | |||
| (when (alchemist-server-contains-end-marker-p output) | |||
| (let* ((output (alchemist-server-prepare-filter-output alchemist-mix-filter-output)) | |||
| (tasks (split-string output "\n")) | |||
| (selected-task (alchemist-mix--completing-read "mix: " tasks)) | |||
| (command (read-shell-command "mix " (concat selected-task " ")))) | |||
| (setq alchemist-mix-filter-output nil) | |||
| (alchemist-mix-execute (list command) current-prefix-arg))))) | |||
| (define-derived-mode alchemist-mix-mode fundamental-mode "Mix Mode" | |||
| "Major mode for presenting Mix tasks. | |||
| \\{alchemist-mix-mode-map}" | |||
| (setq buffer-read-only t) | |||
| (setq-local truncate-lines t) | |||
| (setq-local electric-indent-chars nil)) | |||
| (defun alchemist-mix-execute (command-list &optional prefix) | |||
| "Run a mix task specified by COMMAND-LIST. | |||
| If PREFIX is non-nil, prompt for a mix environment variable." | |||
| (let* ((mix-env (if prefix | |||
| (completing-read "mix env: " alchemist-mix--envs nil nil alchemist-mix-env) | |||
| alchemist-mix-env)) | |||
| (command (alchemist-utils-build-command | |||
| (list (when mix-env (concat "MIX_ENV=" mix-env)) | |||
| alchemist-mix-command command-list)))) | |||
| (alchemist-report-run command "alchemist-mix-report" alchemist-mix-buffer-name 'alchemist-mix-mode))) | |||
| (provide 'alchemist-mix) | |||
| ;;; alchemist-mix.el ends here | |||
| @ -0,0 +1,145 @@ | |||
| ;;; alchemist-phoenix.el --- Minor mode for the Phoenix web framework | |||
| ;; 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: | |||
| ;; Minor mode for the Phoenix web framework | |||
| ;;; Code: | |||
| (require 'alchemist-key) | |||
| (require 'alchemist-project) | |||
| (defgroup alchemist-phoenix nil | |||
| "Minor mode for the Phoenix web framework." | |||
| :prefix "alchemist-phoenix-" | |||
| :group 'alchemist) | |||
| ;;;###autoload | |||
| (defun alchemist-phoenix-project-p () | |||
| "Return non-nil if `default-directory' is inside an Phoenix project." | |||
| (and (alchemist-project-p) | |||
| (file-directory-p (concat (alchemist-project-root) "web")))) | |||
| (defun alchemist-phoenix-find-dir (directory) | |||
| (unless (alchemist-phoenix-project-p) | |||
| (error "Could not find an Phoenix Mix project root.")) | |||
| (alchemist-file-find-files (alchemist-project-root) directory)) | |||
| (defun alchemist-phoenix-find-web () | |||
| (interactive) | |||
| (alchemist-phoenix-find-dir "web")) | |||
| (defun alchemist-phoenix-find-views () | |||
| (interactive) | |||
| (alchemist-phoenix-find-dir "web/views")) | |||
| (defun alchemist-phoenix-find-controllers () | |||
| (interactive) | |||
| (alchemist-phoenix-find-dir "web/controllers")) | |||
| (defun alchemist-phoenix-find-channels () | |||
| (interactive) | |||
| (alchemist-phoenix-find-dir "web/channels")) | |||
| (defun alchemist-phoenix-find-templates () | |||
| (interactive) | |||
| (alchemist-phoenix-find-dir "web/templates")) | |||
| (defun alchemist-phoenix-find-models () | |||
| (interactive) | |||
| (alchemist-phoenix-find-dir "web/models")) | |||
| (defun alchemist-phoenix-find-static () | |||
| (interactive) | |||
| (alchemist-phoenix-find-dir "web/static")) | |||
| (defun alchemist-phoenix-routes (&optional prefix) | |||
| (interactive) | |||
| "Run the Mix task 'phoenix.routes' and list all available Phoenix routes." | |||
| (alchemist-mix-execute '("phoenix.routes") prefix)) | |||
| (defun alchemist-phoenix-router () | |||
| "Open the 'router.ex' file from 'web' directory." | |||
| (interactive) | |||
| (unless (alchemist-phoenix-project-p) | |||
| (error "Could not find an Phoenix Mix project root.")) | |||
| (find-file (concat (alchemist-project-root) "web/router.ex"))) | |||
| (defvar alchemist-phoenix-command-map | |||
| (let ((map (make-sparse-keymap))) | |||
| (define-key map (kbd "n w") #'alchemist-phoenix-find-web) | |||
| (define-key map (kbd "n v") #'alchemist-phoenix-find-views) | |||
| (define-key map (kbd "n c") #'alchemist-phoenix-find-controllers) | |||
| (define-key map (kbd "n l") #'alchemist-phoenix-find-channels) | |||
| (define-key map (kbd "n t") #'alchemist-phoenix-find-templates) | |||
| (define-key map (kbd "n m") #'alchemist-phoenix-find-models) | |||
| (define-key map (kbd "n s") #'alchemist-phoenix-find-static) | |||
| (define-key map (kbd "n r") #'alchemist-phoenix-router) | |||
| (define-key map (kbd "n R") #'alchemist-phoenix-routes) | |||
| map) | |||
| "Keymap for Alchemist Phoenix commands after `alchemist-key-command-prefix'.") | |||
| (fset 'alchemist-phoenix-command-map alchemist-phoenix-command-map) | |||
| (defvar alchemist-phoenix-mode-map | |||
| (let ((map (make-sparse-keymap))) | |||
| (define-key map alchemist-key-command-prefix 'alchemist-phoenix-command-map) | |||
| map) | |||
| "Keymap for Alchemist Phoenix minor mode.") | |||
| (easy-menu-define alchemist-mode-menu alchemist-phoenix-mode-map | |||
| "Menu for Alchemist-Phoenix mode." | |||
| '("Phoenix" | |||
| ("Directory lookup" | |||
| ["Lookup 'web' " alchemist-phoenix-find-web] | |||
| ["Lookup 'web/views' " alchemist-phoenix-find-views] | |||
| ["Lookup 'web/controllers' " alchemist-phoenix-find-controllers] | |||
| ["Lookup 'web/channels' " alchemist-phoenix-find-channels] | |||
| ["Lookup 'web/templates' " alchemist-phoenix-find-templates] | |||
| ["Lookup 'web/models' " alchemist-phoenix-find-models] | |||
| ["Lookup 'web/static'" alchemist-phoenix-find-static]) | |||
| ("Mix tasks" | |||
| ["Run 'phoenix.routes'" alchemist-phoenix-routes]) | |||
| ["Open the 'router.ex' file" alchemist-phoenix-router])) | |||
| ;;;###autoload | |||
| (define-minor-mode alchemist-phoenix-mode | |||
| "Minor mode for Elixir Phoenix web framework projects. | |||
| The following commands are available: | |||
| \\{alchemist-phoenix-mode-map}" | |||
| :lighter " alchemist-phoenix" | |||
| :keymap alchemist-phoenix-mode-map | |||
| :group 'alchemist) | |||
| ;;;###autoload | |||
| (defun alchemist-phoenix-enable-mode () | |||
| (when (alchemist-phoenix-project-p) | |||
| (alchemist-phoenix-mode))) | |||
| ;;;###autoload | |||
| (dolist (hook '(alchemist-mode-hook)) | |||
| (add-hook hook 'alchemist-phoenix-enable-mode)) | |||
| (provide 'alchemist-phoenix) | |||
| ;;; alchemist-phoenix.el ends here | |||
| @ -0,0 +1,11 @@ | |||
| (define-package "alchemist" "20160111.2340" "Elixir tooling integration into Emacs" | |||
| '((elixir-mode "2.2.5") | |||
| (dash "2.11.0") | |||
| (emacs "24.4") | |||
| (company "0.8.0") | |||
| (pkg-info "0.4")) | |||
| :url "http://www.github.com/tonini/alchemist.el" :keywords | |||
| '("languages" "elixir" "elixirc" "mix" "hex" "alchemist")) | |||
| ;; Local Variables: | |||
| ;; no-byte-compile: t | |||
| ;; End: | |||
| @ -0,0 +1,222 @@ | |||
| ;;; alchemist-project.el --- API to identify Elixir mix projects. | |||
| ;; 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: | |||
| ;; API to identify Elixir mix projects. | |||
| ;;; Code: | |||
| (require 'cl-lib) | |||
| (require 'dash) | |||
| (require 'alchemist-utils) | |||
| (require 'alchemist-file) | |||
| (defgroup alchemist-project nil | |||
| "API to identify Elixir mix projects." | |||
| :prefix "alchemist-help-" | |||
| :group 'alchemist) | |||
| (defconst alchemist-project-mix-project-indicator "mix.exs" | |||
| "File which indicates the root directory of an Elixir Mix project.") | |||
| (defconst alchemist-project-hex-pkg-indicator ".hex" | |||
| "File which indicates the root directory of an Elixir Hex package.") | |||
| (defun alchemist-project-elixir-p () | |||
| "Return non-nil if `default-directory' is inside the Elixir source codebase." | |||
| (stringp (alchemist-project-elixir-root))) | |||
| (defun alchemist-project-elixir-root (&optional dir) | |||
| "Return root directory of the Elixir source." | |||
| (let* ((dir (file-name-as-directory (or dir (expand-file-name default-directory)))) | |||
| (present-files (directory-files dir))) | |||
| (cond ((alchemist-project-top-level-dir-p dir) | |||
| nil) | |||
| ((and (-contains-p present-files "eex") | |||
| (-contains-p present-files "elixir") | |||
| (-contains-p present-files "logger") | |||
| (-contains-p present-files "mix") | |||
| (-contains-p present-files "iex") | |||
| (-contains-p present-files "ex_unit")) | |||
| (file-name-directory (directory-file-name dir))) | |||
| (t (alchemist-project-elixir-root (file-name-directory (directory-file-name dir))))))) | |||
| (defun alchemist-project-p () | |||
| "Return non-nil if `default-directory' is inside an Elixir Mix project." | |||
| (stringp (alchemist-project-root))) | |||
| (defun alchemist-project-top-level-dir-p (dir) | |||
| "Return non-nil if DIR is the top level directory." | |||
| (equal dir (file-name-directory (directory-file-name dir)))) | |||
| (defun alchemist-project-root (&optional dir) | |||
| "Return root directory of the current Elixir Mix project. | |||
| It starts walking the directory tree to find the Elixir Mix root directory | |||
| from `default-directory'. If DIR is non-nil it starts walking the | |||
| directory from there instead." | |||
| (let* ((dir (file-name-as-directory (or dir (expand-file-name default-directory)))) | |||
| (present-files (directory-files dir))) | |||
| (cond ((alchemist-project-top-level-dir-p dir) | |||
| nil) | |||
| ((-contains-p present-files alchemist-project-hex-pkg-indicator) | |||
| (alchemist-project-root (file-name-directory (directory-file-name dir)))) | |||
| ((-contains-p present-files alchemist-project-mix-project-indicator) | |||
| dir) | |||
| (t | |||
| (alchemist-project-root (file-name-directory (directory-file-name dir))))))) | |||
| (defun alchemist-project-root-or-default-dir () | |||
| "Return the current Elixir mix project root or `default-directory'." | |||
| (let* ((project-root (alchemist-project-root)) | |||
| (dir (if project-root | |||
| project-root | |||
| default-directory))) | |||
| dir)) | |||
| (defun alchemist-project-toggle-file-and-tests-other-window () | |||
| "Toggle between a file and its tests in other window." | |||
| (interactive) | |||
| (if (alchemist-utils-test-file-p) | |||
| (alchemist-project-open-file-for-current-tests 'find-file-other-window) | |||
| (alchemist-project-open-tests-for-current-file 'find-file-other-window))) | |||
| (defun alchemist-project-toggle-file-and-tests () | |||
| "Toggle between a file and its tests in the current window." | |||
| (interactive) | |||
| (if (alchemist-utils-test-file-p) | |||
| (alchemist-project-open-file-for-current-tests 'find-file) | |||
| (alchemist-project-open-tests-for-current-file 'find-file))) | |||
| (defun alchemist-project-file-under-test (file directory) | |||
| "Return the file which are tested by FILE. | |||
| DIRECTORY is the place where the file under test is located." | |||
| (let* ((filename (file-relative-name file (alchemist-project-root))) | |||
| (filename (replace-regexp-in-string "^test" directory filename)) | |||
| (filename (replace-regexp-in-string "_test\.exs$" "\.ex" filename))) | |||
| (concat (alchemist-project-root) filename))) | |||
| (defun alchemist-project-open-file-for-current-tests (opener) | |||
| "Visit the implementation file for the current buffer with OPENER." | |||
| (let* ((filename (alchemist-project-file-under-test (buffer-file-name) "web")) | |||
| (filename (if (file-exists-p filename) | |||
| filename | |||
| (alchemist-project-file-under-test (buffer-file-name) "lib")))) | |||
| (funcall opener filename))) | |||
| (defun alchemist-project-open-tests-for-current-file (opener) | |||
| "Visit the test file for the current buffer with OPENER." | |||
| (let* ((filename (file-relative-name (buffer-file-name) (alchemist-project-root))) | |||
| (filename (replace-regexp-in-string "^lib/" "test/" filename)) | |||
| (filename (replace-regexp-in-string "^web/" "test/" filename)) | |||
| (filename (replace-regexp-in-string "\.ex$" "_test\.exs" filename)) | |||
| (filename (format "%s/%s" (alchemist-project-root) filename))) | |||
| (if (file-exists-p filename) | |||
| (funcall opener filename) | |||
| (if (y-or-n-p "No test file found; create one now?") | |||
| (alchemist-project--create-test-for-current-file | |||
| filename (current-buffer)) | |||
| (message "No test file found."))))) | |||
| (defun alchemist-project--create-test-for-current-file (filename buffer) | |||
| "Creates and populates a test module, FILENAME, for the code in BUFFER. | |||
| The module name given to the test module is determined from the name of the | |||
| first module defined in BUFFER." | |||
| (let* ((directory-name (file-name-directory filename)) | |||
| (module-name (alchemist-project--grok-module-name buffer)) | |||
| (test-module-name (concat module-name "Test"))) | |||
| (unless (file-exists-p directory-name) | |||
| (make-directory (file-name-directory filename) t)) | |||
| (alchemist-project--insert-test-boilerplate | |||
| (find-file-other-window filename) test-module-name))) | |||
| (defun alchemist-project--grok-module-name (buffer) | |||
| "Determines the name of the first module defined in BUFFER." | |||
| (save-excursion | |||
| (with-current-buffer buffer | |||
| (goto-char (point-min)) | |||
| (re-search-forward "defmodule\\s-\\(.+?\\)\\s-?,?\\s-do") | |||
| (match-string 1)))) | |||
| (defun alchemist-project--insert-test-boilerplate (buffer module) | |||
| "Inserts ExUnit boilerplate for MODULE in BUFFER. | |||
| Point is left in a convenient location." | |||
| (with-current-buffer buffer | |||
| (insert (concat "defmodule " module " do\n" | |||
| " use ExUnit.Case\n\n\n" | |||
| "end\n")) | |||
| (goto-char (point-min)) | |||
| (beginning-of-line 4) | |||
| (indent-according-to-mode))) | |||
| (defun alchemist-project-run-tests-for-current-file () | |||
| "Run the tests related to the current file." | |||
| (interactive) | |||
| (alchemist-project-open-tests-for-current-file 'alchemist-mix-test-file)) | |||
| (defun alchemist-project-create-file () | |||
| "Create a file under lib/ in the current project. | |||
| The newly created buffer is filled with a module definition based on the file name." | |||
| (interactive) | |||
| (let ((root (alchemist-project-root))) | |||
| (if (not root) | |||
| (message "You're not in a Mix project") | |||
| (let* ((lib-path (concat root "lib/")) | |||
| (abs-path (read-file-name "New file in lib/: " lib-path)) | |||
| (abs-path (alchemist-utils-add-ext-to-path-if-not-present abs-path ".ex")) | |||
| (relative-path (file-relative-name abs-path lib-path))) | |||
| (if (file-readable-p abs-path) | |||
| (message "%s already exists" relative-path) | |||
| (make-directory (file-name-directory abs-path) t) | |||
| (find-file abs-path) | |||
| (insert (concat "defmodule " | |||
| (alchemist-utils-path-to-module-name relative-path) | |||
| " do\n" | |||
| " \n" | |||
| "end\n")) | |||
| (goto-char (point-min)) | |||
| (beginning-of-line 2) | |||
| (back-to-indentation)))))) | |||
| (defun alchemist-project-name () | |||
| "Return the name of the current Elixir Mix project." | |||
| (if (alchemist-project-p) | |||
| (car (cdr (reverse (split-string (alchemist-project-root) "/")))) | |||
| "")) | |||
| (defun alchemist-project-find-dir (directory) | |||
| (unless (alchemist-project-p) | |||
| (error "Could not find an Elixir Mix project root.")) | |||
| (alchemist-file-find-files (alchemist-project-root) directory)) | |||
| (defun alchemist-project-find-lib () | |||
| (interactive) | |||
| (alchemist-project-find-dir "lib")) | |||
| (defun alchemist-project-find-test () | |||
| (interactive) | |||
| (alchemist-project-find-dir "test")) | |||
| (provide 'alchemist-project) | |||
| ;;; alchemist-project.el ends here | |||
| @ -0,0 +1,201 @@ | |||
| ;;; alchemist-refcard.el --- Generates a refcard of alchemist functionality | |||
| ;; 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: | |||
| ;; Generates a refcard of alchemist functionality | |||
| ;;; Code: | |||
| (require 'cl-lib) | |||
| (require 'dash) | |||
| (require 'tabulated-list) | |||
| ;; Tell the byte compiler about autoloaded functions from packages | |||
| (eval-when-compile | |||
| (declare-function alchemist-mode "alchemist.el") | |||
| (declare-function alchemist-version "alchemist.el")) | |||
| (defgroup alchemist-refcard nil | |||
| "Generate a refcard of alchemist functionality." | |||
| :prefix "alchemist-" | |||
| :group 'applications) | |||
| (defconst alchemist-refcard--buffer-name "*alchemist-refcard*" | |||
| "Name of Alchemist-Refcard mode buffer.") | |||
| (defconst alchemist-refcard-list-format | |||
| [("" 55 t) | |||
| ("" 35 t)] | |||
| "List format.") | |||
| (defvar alchemist-refcard-mode-map | |||
| (let ((map (make-sparse-keymap))) | |||
| (define-key map (kbd "i") 'alchemist-refcard--describe-funtion-at-point) | |||
| (define-key map (kbd "q") 'quit-window) | |||
| map) | |||
| "Keymap for `alchemist-refcard-mode'.") | |||
| (defun alchemist-refcard--get-keybinding (function-name) | |||
| (let* ((keys (where-is-internal (intern function-name))) | |||
| (keys (-map (lambda (k) | |||
| (let ((key (format "%s" k))) | |||
| (if (string-match-p "menu-bar" key) | |||
| nil | |||
| k))) keys)) | |||
| (keys (-remove 'null keys))) | |||
| (if keys | |||
| (progn | |||
| (mapconcat (lambda (k) (key-description k)) keys " , ")) | |||
| ""))) | |||
| (defun alchemist-refcard--tabulated-list-entries () | |||
| (alchemist-mode +1) ;; needs to be enabled for fetching current keybindings | |||
| (let ((rows (list (alchemist-refcard--build-empty-tabulated-row) | |||
| (alchemist-refcard--build-tabulated-refcard-title-row (format "Alchemist Refcard v%s" (alchemist-version))) | |||
| (alchemist-refcard--build-empty-tabulated-row) | |||
| (alchemist-refcard--build-tabulated-title-row "Mix") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-mix") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-mix-compile") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-mix-run") | |||
| (alchemist-refcard--build-empty-tabulated-row) | |||
| (alchemist-refcard--build-tabulated-title-row "Testing") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-mix-test") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-mix-rerun-last-test") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-mix-test-file") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-mix-test-this-buffer") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-mix-test-at-point") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-test-toggle-test-report-display") | |||
| (alchemist-refcard--build-empty-tabulated-row) | |||
| (alchemist-refcard--build-tabulated-title-row "Compilation") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-compile") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-compile-file") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-compile-this-buffer") | |||
| (alchemist-refcard--build-empty-tabulated-row) | |||
| (alchemist-refcard--build-tabulated-title-row "Execution") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-execute") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-execute-file") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-execute-this-buffer") | |||
| (alchemist-refcard--build-empty-tabulated-row) | |||
| (alchemist-refcard--build-tabulated-title-row "Documentation Lookup") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-help") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-help-history") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-help-search-at-point") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-refcard") | |||
| (alchemist-refcard--build-empty-tabulated-row) | |||
| (alchemist-refcard--build-tabulated-title-row "Definition Lookup") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-goto-definition-at-point") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-goto-jump-back") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-goto-jump-to-previous-def-symbol") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-goto-jump-to-next-def-symbol") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-goto-list-symbol-definitions") | |||
| (alchemist-refcard--build-empty-tabulated-row) | |||
| (alchemist-refcard--build-tabulated-title-row "Project") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-project-find-test") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-project-toggle-file-and-tests") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-project-toggle-file-and-tests-other-window") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-project-run-tests-for-current-file") | |||
| (alchemist-refcard--build-empty-tabulated-row) | |||
| (alchemist-refcard--build-tabulated-title-row "IEx") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-iex-run") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-iex-project-run") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-iex-send-current-line") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-iex-send-current-line-and-go") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-iex-send-region") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-iex-send-region-and-go") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-iex-compile-this-buffer") | |||
| (alchemist-refcard--build-empty-tabulated-row) | |||
| (alchemist-refcard--build-tabulated-title-row "Eval") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-eval-current-line") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-eval-print-current-line") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-eval-quoted-current-line") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-eval-print-quoted-current-line") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-eval-region") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-eval-print-region") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-eval-quoted-region") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-eval-print-quoted-region") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-eval-buffer") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-eval-print-buffer") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-eval-quoted-buffer") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-eval-print-quoted-buffer") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-eval-close-popup") | |||
| (alchemist-refcard--build-empty-tabulated-row) | |||
| (alchemist-refcard--build-tabulated-title-row "Macroexpand") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-macroexpand-once-current-line") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-macroexpand-once-print-current-line") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-macroexpand-current-line") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-macroexpand-print-current-line") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-macroexpand-once-region") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-macroexpand-once-print-region") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-macroexpand-region") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-macroexpand-print-region") | |||
| (alchemist-refcard--build-tabulated-row "alchemist-macroexpand-close-popup")))) | |||
| (alchemist-mode -1) ;; disable it after getting the current keybindings | |||
| rows)) | |||
| (defun alchemist-refcard--build-empty-tabulated-row () | |||
| (list "" `[,"" ""])) | |||
| (defun alchemist-refcard--build-tabulated-row (function-name) | |||
| (list function-name `[,function-name | |||
| ,(propertize (alchemist-refcard--get-keybinding function-name) 'face font-lock-builtin-face)])) | |||
| (defun alchemist-refcard--build-tabulated-refcard-title-row (title) | |||
| (list "" `[,(propertize title 'face font-lock-variable-name-face) ""])) | |||
| (defun alchemist-refcard--build-tabulated-title-row (title) | |||
| (list "" `[,(propertize title 'face font-lock-constant-face) ""])) | |||
| (defun alchemist-refcard--describe-funtion-at-point () | |||
| (interactive) | |||
| (let ((function-name (get-text-property (point) 'tabulated-list-id))) | |||
| (when (not (alchemist-utils-empty-string-p function-name)) | |||
| (describe-function (intern function-name))))) | |||
| (defun alchemist-refcard--buffer () | |||
| "Return alchemist-refcard buffer if it exists." | |||
| (get-buffer alchemist-refcard--buffer-name)) | |||
| (define-derived-mode alchemist-refcard-mode tabulated-list-mode "Alchemist" | |||
| "Alchemist refcard mode." | |||
| (buffer-disable-undo) | |||
| (kill-all-local-variables) | |||
| (setq truncate-lines t) | |||
| (setq mode-name "Alchemist-Refcard") | |||
| (setq-local alchemist-test-status-modeline nil) | |||
| (use-local-map alchemist-refcard-mode-map) | |||
| (setq tabulated-list-format alchemist-refcard-list-format) | |||
| (setq tabulated-list-entries 'alchemist-refcard--tabulated-list-entries) | |||
| (tabulated-list-print)) | |||
| ;;;###autoload | |||
| (defun alchemist-refcard () | |||
| "Generate an Alchemist refcard of all the features." | |||
| (interactive) | |||
| (let ((buffer-p (alchemist-refcard--buffer)) | |||
| (buffer (get-buffer-create alchemist-refcard--buffer-name))) | |||
| (pop-to-buffer buffer) | |||
| (unless buffer-p | |||
| (alchemist-refcard-mode)))) | |||
| (provide 'alchemist-refcard) | |||
| ;;; alchemist-refcard.el ends here | |||
| @ -0,0 +1,167 @@ | |||
| ;;; alchemist-report.el --- Run command in a process and handles buffer of it | |||
| ;; 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: | |||
| ;; Run command in a process and handles buffer output and display | |||
| ;;; Code: | |||
| (require 'ansi-color) | |||
| (require 'alchemist-project) | |||
| (defgroup alchemist-report nil | |||
| "Run command in a process and handles buffer output and display" | |||
| :prefix "alchemist-report-" | |||
| :group 'alchemist) | |||
| (defvar alchemist-report-on-exit nil) | |||
| (defvar alchemist-report-on-exit-function nil) | |||
| (defvar alchemist-report-on-render nil) | |||
| (defvar alchemist-report-on-render-function nil) | |||
| (defvar alchemist-report--last-run-status nil) | |||
| (defvar alchemist-report-mode-name nil) | |||
| (defun alchemist-report--kill-process (process) | |||
| "Interrupt and kill the running report PROCESS." | |||
| (when process | |||
| (let ((mode-name (replace-regexp-in-string ":.+$" "" mode-name))) | |||
| (if (or (not (eq (process-status process) 'run)) | |||
| (eq (process-query-on-exit-flag process) nil) | |||
| (yes-or-no-p | |||
| (format "A %s process already running; kill it? " | |||
| mode-name))) | |||
| (condition-case () | |||
| (progn | |||
| (interrupt-process process) | |||
| (sit-for 1) | |||
| (delete-process process)) | |||
| (error nil)) | |||
| (error "Cannot have two processes in `%s' at once" | |||
| (buffer-name)))))) | |||
| (defun alchemist-report--sentinel (process status) | |||
| "Sentinel for test report buffer." | |||
| (if (memq (process-status process) '(exit signal)) | |||
| (let ((buffer (process-buffer process))) | |||
| (if (null (buffer-name buffer)) | |||
| (set-process-buffer process nil) | |||
| (progn | |||
| (alchemist-report--render-report buffer) | |||
| (alchemist-report--handle-exit status buffer) | |||
| (alchemist-report-update-mode-name process) | |||
| (delete-process process)))))) | |||
| (defun alchemist-report--render-report (buffer) | |||
| "Call the defined render functions for the BUFFER." | |||
| (when alchemist-report-on-render-function | |||
| (funcall alchemist-report-on-render-function buffer))) | |||
| (defun alchemist-report--handle-exit (status buffer) | |||
| "Call the defined exit function specified in `alchemist-report-on-exit-function'. | |||
| Argument for the exit function is the STATUS and BUFFER of the finished process." | |||
| (alchemist-report--store-process-status status) | |||
| (when alchemist-report-on-exit-function | |||
| (funcall alchemist-report-on-exit-function status buffer))) | |||
| (defun alchemist-report--store-process-status (status) | |||
| "Store STATUS of the last finished process." | |||
| (setq alchemist-report--last-run-status status)) | |||
| (defun alchemist-report--last-run-successful-p () | |||
| "Return non-nil if the last process successfully finished." | |||
| (when (string-prefix-p "finished" alchemist-report--last-run-status) t)) | |||
| (defun alchemist-report-filter (process output) | |||
| "Process filter for report buffers." | |||
| (with-current-buffer (process-buffer process) | |||
| (let* ((buffer-read-only nil) | |||
| (output (if (string= (process-name process) alchemist-test-report-process-name) | |||
| (alchemist-test-clean-compilation-output output) | |||
| output)) | |||
| (moving (= (point) (process-mark process)))) | |||
| (save-excursion | |||
| (goto-char (process-mark process)) | |||
| (insert output) | |||
| (set-marker (process-mark process) (point)) | |||
| (ansi-color-apply-on-region (point-min) (point-max))) | |||
| (if moving (goto-char (process-mark process)))))) | |||
| (defun alchemist-report-update-mode-name (process) | |||
| "Update the `mode-name' with the status of PROCESS." | |||
| (with-current-buffer (process-buffer process) | |||
| (setq-local mode-name (format "%s:%s" | |||
| (replace-regexp-in-string ":.+$" "" mode-name) | |||
| (process-status process))))) | |||
| (defun alchemist-report-interrupt-current-process () | |||
| "Interrupt the current running report process." | |||
| (interactive) | |||
| (let ((buffer (current-buffer)) | |||
| (name (replace-regexp-in-string ":.+" "" mode-name))) | |||
| (if (get-buffer-process buffer) | |||
| (interrupt-process (get-buffer-process buffer)) | |||
| (error "The [%s] process is not running" (downcase name))))) | |||
| (defun alchemist-report-cleanup-process-buffer (buffer) | |||
| "Clean the content BUFFER of process. | |||
| If there is already a running process, ask for interrupting it." | |||
| (with-current-buffer buffer | |||
| (let ((inhibit-read-only t) | |||
| (process (get-buffer-process buffer))) | |||
| (erase-buffer)))) | |||
| (defun alchemist-report-display-buffer (buffer) | |||
| "Display the BUFFER." | |||
| (display-buffer buffer)) | |||
| (defun alchemist-report-activate-mode (mode buffer) | |||
| "Enable MODE inside BUFFER." | |||
| (with-current-buffer buffer | |||
| (funcall mode) | |||
| (setq-local truncate-lines t) ;; Do not display continuation lines. | |||
| (setq-local window-point-insertion-type t))) | |||
| (defun alchemist-report-run (command process-name buffer-name mode &optional on-exit hidden) | |||
| "Run COMMAND in a new process called PROCESS-NAME. | |||
| The output of PROCESS-NAME will be displayed in BUFFER-NAME. | |||
| After displaying BUFFER-NAME, the MODE function will be called within. | |||
| Optional ON-EXIT and HIDDEN functions could be defined. | |||
| The function ON-EXIT will be called when PROCESS-NAME is finished. | |||
| The HIDDEN variable defines if PROCESS-NAME should run in the background." | |||
| (let* ((buffer (get-buffer-create buffer-name)) | |||
| (default-directory (alchemist-project-root-or-default-dir))) | |||
| (alchemist-report-cleanup-process-buffer buffer) | |||
| (alchemist-report--kill-process (get-buffer-process buffer)) | |||
| (start-process-shell-command process-name buffer command) | |||
| (when on-exit | |||
| (setq alchemist-report-on-exit-function on-exit)) | |||
| (set-process-sentinel (get-buffer-process buffer) 'alchemist-report--sentinel) | |||
| (set-process-filter (get-buffer-process buffer) 'alchemist-report-filter) | |||
| (alchemist-report-activate-mode mode buffer) | |||
| (if (not hidden) | |||
| (alchemist-report-display-buffer buffer)) | |||
| (alchemist-report-update-mode-name (get-buffer-process buffer)))) | |||
| (provide 'alchemist-report) | |||
| ;;; alchemist-report.el ends here | |||
| @ -0,0 +1,205 @@ | |||
| ;;; alchemist-scope.el --- Provides information about Elixir source code context -*- 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: | |||
| ;; Provides information about the Elixir source code context. | |||
| ;;; Code: | |||
| (require 'dash) | |||
| (defgroup alchemist-scope nil | |||
| "Provides information about the Elixir source code context." | |||
| :prefix "alchemist-scope-" | |||
| :group 'alchemist) | |||
| (defconst alchemist-scope-defmodule-regex "defmodule \\([A-Za-z\._]+\\)\s+" | |||
| "The regex for matching Elixir defmodule macro.") | |||
| (defconst alchemist-scope-alias-regex | |||
| "^\s+alias\s+\\([-:_A-Za-z0-9,\.\?!\]+\\)\\(\s*,\s*as:\s*\\)?\\([-_A-Za-z0-9,\.\?!\]+\\)?\n" | |||
| "The regex for matching Elixir alias definitions. | |||
| Example: | |||
| alias Phoenix.Router.Resource, as: Special") | |||
| (defconst alchemist-scope-alias-regex-two | |||
| "^\s+alias\s+\\([-:_A-Za-z0-9,\.\?!\]+\\)\.{\\([-:_A-Za-z0-9\s,\.\?!\]+\\)}\n" | |||
| "The regex for matching Elixir alias definitions. | |||
| Example: | |||
| alias List.Chars.{Atom, Float}") | |||
| (defconst alchemist-scope-use-regex | |||
| "^\s+use\s+\\([A-Za-z0-9\.]+\\)" | |||
| "The regex for matching Elixir use definitions.") | |||
| (defconst alchemist-scope-import-regex | |||
| "^\s+import\s+\\([A-Za-z0-9\.]+\\)" | |||
| "The regex for matching Elixir import definitions.") | |||
| (defun alchemist-scope-inside-string-p () | |||
| "Return non-nil if `point' is inside a string or heredoc." | |||
| (let* ((pos (point)) | |||
| (parse-info (syntax-ppss pos))) | |||
| (or (and (nth 3 parse-info) | |||
| (nth 8 parse-info)) | |||
| (and (looking-at "\"\"\"\\|'''\\|\"\\|\'") | |||
| (match-beginning 0))))) | |||
| (defun alchemist-scope-inside-module-p () | |||
| "Return non-nil if `point' is currently inside a module." | |||
| (save-excursion | |||
| (end-of-line) | |||
| (let ((found-flag-p nil) | |||
| (found-p nil)) | |||
| (while (and (not found-flag-p) | |||
| (re-search-backward alchemist-scope-defmodule-regex nil t)) | |||
| (when (not (alchemist-scope-inside-string-p)) | |||
| (setq found-flag-p t) | |||
| (setq found-p t))) | |||
| found-p))) | |||
| (defun alchemist-scope-module () | |||
| "Return name from the current defmodule." | |||
| (save-excursion | |||
| (let ((found-flag-p nil) | |||
| (module-name "")) | |||
| (save-match-data | |||
| (while (and (not found-flag-p) | |||
| (re-search-backward alchemist-scope-defmodule-regex nil t)) | |||
| (when (not (alchemist-scope-inside-string-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-scope-aliases () | |||
| "Return aliases from the current module." | |||
| (let* ((aliases '()) | |||
| (context (alchemist-scope-module))) | |||
| (save-excursion | |||
| (when (alchemist-scope-inside-module-p) | |||
| (end-of-line) | |||
| ;; alias definition like: | |||
| ;; | |||
| ;; alias Phoenix.Router.Resource, as: Special | |||
| (while (re-search-backward alchemist-scope-alias-regex nil t) | |||
| (when (and | |||
| (not (alchemist-scope-inside-string-p)) | |||
| (equal context (alchemist-scope-module))) | |||
| (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 (alchemist-utils-remove-dot-at-the-end alias) | |||
| (alchemist-utils-remove-dot-at-the-end as)))))))) | |||
| ;; alias definition like: | |||
| ;; | |||
| ;; alias List.Chars.{Atom, Float} | |||
| (while (re-search-backward alchemist-scope-alias-regex-two nil t) | |||
| (when (and | |||
| (not (alchemist-scope-inside-string-p)) | |||
| (equal context (alchemist-scope-module))) | |||
| (let* ((prefix (match-string 1)) | |||
| (alias-collection (if (match-string 2) (split-string (match-string 2) ",") nil))) | |||
| (-map (lambda (alias) | |||
| (let* ((alias (replace-regexp-in-string "\s+" "" alias)) | |||
| (namespace (format "%s.%s" prefix alias))) | |||
| (setq aliases (append aliases (list (list (alchemist-utils-remove-dot-at-the-end namespace) | |||
| (alchemist-utils-remove-dot-at-the-end alias))))))) | |||
| alias-collection)))))) | |||
| aliases)) | |||
| (defun alchemist-scope--modules (regex) | |||
| (let ((modules '()) | |||
| (context (alchemist-scope-module))) | |||
| (save-excursion | |||
| (when (not (alchemist-utils-empty-string-p context)) | |||
| (while (re-search-backward regex nil t) | |||
| (when (and (match-string 1) | |||
| (not (alchemist-scope-inside-string-p)) | |||
| (equal context (alchemist-scope-module))) | |||
| (cl-pushnew (substring-no-properties (match-string 1)) modules)))) | |||
| modules))) | |||
| (defun alchemist-scope-use-modules () | |||
| "Return `use' introduced module names from the current module." | |||
| (alchemist-scope--modules alchemist-scope-use-regex)) | |||
| (defun alchemist-scope-import-modules () | |||
| "Return `import' introduced module names from the current module." | |||
| (alchemist-scope--modules alchemist-scope-import-regex)) | |||
| (defun alchemist-scope-all-modules () | |||
| "Return `use' and `import' introduced modules from the current module." | |||
| (let ((current (alchemist-scope-module)) | |||
| (use (alchemist-scope-use-modules)) | |||
| (import (alchemist-scope-import-modules)) | |||
| (modules '())) | |||
| (push current modules) | |||
| (push use modules) | |||
| (push import modules) | |||
| (-flatten modules))) | |||
| (defun alchemist-scope-extract-module (expr) | |||
| "Extract module from EXPR." | |||
| (let* ((parts (split-string expr "\\.")) | |||
| (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)) | |||
| (alchemist-utils-remove-dot-at-the-end (mapconcat 'concat parts "."))))) | |||
| (defun alchemist-scope-extract-function (expr) | |||
| "Extract function from EXPR." | |||
| (let* ((parts (split-string expr "\\.")) | |||
| (function (car (last parts))) | |||
| (case-fold-search nil)) | |||
| (when (and function | |||
| (string-match-p "^[a-z_\?!]+" function)) | |||
| function))) | |||
| (defun alchemist-scope-alias-full-path (module) | |||
| "Solve the full path for the MODULE alias." | |||
| (if (not (alchemist-utils-empty-string-p module)) | |||
| (let* ((aliases (-map (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-scope-aliases))) | |||
| (aliases (delete nil aliases))) | |||
| (if aliases | |||
| (car aliases) | |||
| module)))) | |||
| (defun alchemist-scope-expression () | |||
| "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)))) | |||
| (provide 'alchemist-scope) | |||
| ;;; alchemist-scope.el ends here | |||
| @ -0,0 +1,211 @@ | |||
| ;;; alchemist-server.el --- Interface to the Alchemist Elixir server. -*- 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: | |||
| ;; Interface to the Alchemist Elixir server. | |||
| ;;; Code: | |||
| (require 'alchemist-execute) | |||
| (defgroup alchemist-server nil | |||
| "Interface to the Alchemist Elixir server." | |||
| :prefix "alchemist-server-" | |||
| :group 'alchemist) | |||
| (defvar alchemist-server-processes '() | |||
| "Store running Alchemist server processes.") | |||
| (defvar alchemist-server-env "dev" | |||
| "Default environment in for the Alchemist server.") | |||
| (defvar alchemist-server-envs '("dev" "prod" "test" "shared") | |||
| "List of available Alchemist server environments.") | |||
| (defconst alchemist-server | |||
| (concat (file-name-directory load-file-name) "alchemist-server/run.exs") | |||
| "Path to the Alchemist server file.") | |||
| (defconst alchemist-server-command | |||
| (format "%s %s %s" | |||
| alchemist-execute-command | |||
| alchemist-server | |||
| alchemist-server-env) | |||
| "Alchemist server command.") | |||
| (defconst alchemist-server-codes '((server-eval "EVAL") | |||
| (server-defl "DEFL") | |||
| (server-info "INFO") | |||
| (server-docl "DOCL") | |||
| (server-comp "COMP")) | |||
| "Alchemist server API codes.") | |||
| (defun alchemist-server-start (env) | |||
| "Start alchemist server for the current mix project in specific ENV. | |||
| If a server already running, the current one will be killed and new one | |||
| will be started instead." | |||
| (interactive (list | |||
| (completing-read (format "(Alchemist-Server) run in environment: (default: %s) " alchemist-server-env) | |||
| alchemist-server-envs nil nil nil))) | |||
| (when (alchemist-server-process-p) | |||
| (kill-process (alchemist-server-process))) | |||
| (alchemist-server-start-in-env env)) | |||
| (defun alchemist-server-start-if-not-running () | |||
| "Start a new Alchemist server if not already running. | |||
| An Alchemist server will be started for the current Elixir mix project." | |||
| (unless (alchemist-server-process-p) | |||
| (alchemist-server-start-in-env alchemist-server-env))) | |||
| (defun alchemist-server-start-in-env (env) | |||
| "Start an Alchemist server with the 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) | |||
| "Store PROCESS in `alchemist-server-processes'." | |||
| (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 () | |||
| "Return non-nil if a process for the current | |||
| Elixir mix project is live." | |||
| (process-live-p (alchemist-server-process))) | |||
| (defun alchemist-server-process () | |||
| "Return process for the current Elixir mix project." | |||
| (cdr (assoc (alchemist-server-process-name) alchemist-server-processes))) | |||
| (defun alchemist-server-process-name () | |||
| "Return process name for the current Elixir mix project." | |||
| (let* ((process-name (if (alchemist-project-elixir-p) | |||
| "alchemist-server" | |||
| (alchemist-project-root))) | |||
| (process-name (if process-name | |||
| process-name | |||
| "alchemist-server"))) | |||
| process-name)) | |||
| (defun alchemist-server-api-code (symbol) | |||
| "Return Alchemist server API code for SYMBOL." | |||
| (car (cdr (assoc symbol alchemist-server-codes)))) | |||
| (defconst alchemist-server-code-end-marker-regex | |||
| (format "END-OF-\\(%s\\|%s\\|%s\\|%s\\|%s\\)$" | |||
| (alchemist-server-api-code 'server-eval) | |||
| (alchemist-server-api-code 'server-defl) | |||
| (alchemist-server-api-code 'server-info) | |||
| (alchemist-server-api-code 'server-docl) | |||
| (alchemist-server-api-code 'server-comp)) | |||
| "Regular expression to identify Alchemist server API end markers.") | |||
| (defun alchemist-server-contains-end-marker-p (string) | |||
| "Return non-nil if STRING contain an Alchemist server API end marker." | |||
| (string-match-p alchemist-server-code-end-marker-regex string)) | |||
| (defun alchemist-server-build-request-string (code &optional args) | |||
| "Build Alchemist server request string for CODE. | |||
| If ARGS available add them to the request string." | |||
| (let* ((code (car (cdr (assoc code alchemist-server-codes))))) | |||
| (if args | |||
| (format "%s %s\n" code args) | |||
| (format "%s\n" code)))) | |||
| (defun alchemist-server-prepare-filter-output (output) | |||
| "Clean OUTPUT by remove Alchemist server API end markes." | |||
| (let* ((output (apply #'concat (reverse output))) | |||
| (output (replace-regexp-in-string alchemist-server-code-end-marker-regex "" output)) | |||
| (output (replace-regexp-in-string "\n+$" "" output))) | |||
| output)) | |||
| (defun alchemist-server-send-request (string filter) | |||
| "Send STRING to Alchemist server API and set FILTER to process." | |||
| (alchemist-server-start-if-not-running) | |||
| (set-process-filter (alchemist-server-process) filter) | |||
| (process-send-string (alchemist-server-process) string)) | |||
| (defun alchemist-server-goto (args filter) | |||
| "Make an Alchemist server source request with ARGS. | |||
| Process server respond with FILTER." | |||
| (alchemist-server-start-if-not-running) | |||
| (alchemist-server-send-request (alchemist-server-build-request-string 'server-defl args) filter)) | |||
| (defun alchemist-server-info (args filter) | |||
| "Make an Alchemist server mix request. | |||
| Process server respond with FILTER." | |||
| (alchemist-server-start-if-not-running) | |||
| (alchemist-server-send-request (alchemist-server-build-request-string 'server-info args) filter)) | |||
| (defun alchemist-server-help-with-modules (filter) | |||
| "Make an Alchemist server modules request. | |||
| Process server respond with FILTER." | |||
| (alchemist-server-start-if-not-running) | |||
| (alchemist-server-send-request (alchemist-server-build-request-string 'server-info) filter)) | |||
| (defun alchemist-server-help (args filter) | |||
| "Make an Alchemist server doc request with ARGS. | |||
| Process server respond with FILTER." | |||
| (alchemist-server-start-if-not-running) | |||
| (alchemist-server-send-request (alchemist-server-build-request-string 'server-docl args) filter)) | |||
| (defun alchemist-server-eval (args filter) | |||
| "Make an Alchemist server evaluate request with FILE. | |||
| Process server respond with FILTER." | |||
| (alchemist-server-start-if-not-running) | |||
| (alchemist-server-send-request (alchemist-server-build-request-string 'server-eval args) filter)) | |||
| (defun alchemist-server-complete-candidates (args filter) | |||
| "Make an Alchemist server complete request with ARGS. | |||
| Process server respond with FILTER." | |||
| (alchemist-server-start-if-not-running) | |||
| (alchemist-server-send-request (alchemist-server-build-request-string 'server-comp args) filter)) | |||
| (defun alchemist-server-status () | |||
| "Report the server status for the current Elixir project." | |||
| (interactive) | |||
| (message "Alchemist-Server-Status: [Project: %s Status: %s]" | |||
| (alchemist-server-process-name) | |||
| (if (alchemist-server-process-p) | |||
| "Connected" | |||
| "Not Connected"))) | |||
| (provide 'alchemist-server) | |||
| ;;; alchemist-server.el ends here | |||
| @ -0,0 +1,2 @@ | |||
| test/sandbox | |||
| .cask | |||
| @ -0,0 +1,12 @@ | |||
| language: elixir | |||
| elixir: | |||
| - 1.1.1 | |||
| - 1.2.0-rc.0 | |||
| otp_release: | |||
| - 18.0 | |||
| sudo: false | |||
| install: mix local.hex --force | |||
| script: | |||
| - make | |||
| notifications: | |||
| irc: "irc.freenode.org#emacs-elixir" | |||
| @ -0,0 +1,35 @@ | |||
| ELIXIR = elixir | |||
| VERSION = $(shell git describe --tags --abbrev=0 | sed 's/^v//') | |||
| NO_COLOR=\033[0m | |||
| INFO_COLOR=\033[2;32m | |||
| STAT_COLOR=\033[2;33m | |||
| all: test | |||
| test: test_server | |||
| ${MAKE} test_helpers | |||
| ${MAKE} test_api | |||
| test_server: | |||
| @ echo "\n$(INFO_COLOR)Run server tests: $(NO_COLOR)\n" | |||
| $(ELIXIR) test/server_test.exs | |||
| test_helpers: | |||
| @ echo "\n$(INFO_COLOR)Run helper tests: $(NO_COLOR)\n" | |||
| $(ELIXIR) test/helpers/module_info_test.exs | |||
| $(ELIXIR) test/helpers/complete_test.exs | |||
| test_api: | |||
| @ echo "\n$(INFO_COLOR)Run api tests: $(NO_COLOR)\n" | |||
| $(ELIXIR) test/api/docl_test.exs | |||
| $(ELIXIR) test/api/comp_test.exs | |||
| $(ELIXIR) test/api/defl_test.exs | |||
| api_completer: | |||
| @ echo "\n$(INFO_COLOR)Run api tests: $(NO_COLOR)\n" | |||
| $(ELIXIR) test/api_test.exs | |||
| .PHONY: test test_server test_helpers test_api | |||
| @ -0,0 +1,161 @@ | |||
| [](http://www.gnu.org/licenses/gpl-3.0.txt) | |||
| [](https://travis-ci.org/tonini/alchemist-server) | |||
| **INFO:** The Alchemist-Server is in Beta status and the API will most likey change until the first release. Feedback and critic are highly appreciated though. | |||
| # Alchemist Server | |||
| The Alchemist-Server operates as an informant for a specific desired | |||
| Elixir Mix project and serves with informations as the following: | |||
| * Completion for Modules and functions. | |||
| * Documentation lookup for Modules and functions. | |||
| * Code evaluation and quoted representation of code. | |||
| * Definition lookup of code. | |||
| * Listing of all available Mix tasks. | |||
| * Listing of all available Modules with documentation. | |||
| # Usage | |||
| The server needs to be started inside an Elixir mix project like below: | |||
| ``` | |||
| $ cd elixir_project | |||
| $ elixir path/to/alchemist-server/run.exs dev | |||
| ``` | |||
| The Alchemist-Server API is `STDIN/STDOUT` based, when input sent to a | |||
| running server process it responds by sending information back to the `STDOUT`. | |||
| A request consisting of two parts, the request type and the request arguments. | |||
| Example for a completion request: | |||
| ``` | |||
| [type] [arguments] | |||
| COMP { "def", [ context: Elixir, imports: [Enum], aliases: [{MyList, List}] ] } | |||
| ``` | |||
| # API | |||
| ## Completion | |||
| Return a completion list of all the available candidates. | |||
| ``` | |||
| COMP | |||
| COMP { "def", [ context: Elixir, imports: [], aliases: [] ] } | |||
| COMP { "List.fla", [ context: Elixir, imports: [], aliases: [] ] } | |||
| ``` | |||
| ## Documentation lookup | |||
| Return the documentation. | |||
| ``` | |||
| DOCL { "defmodule", [ context: Elixir, imports: [], aliases: [] ] } | |||
| DOCL { "List.flatten/1", [ context: Elixir, imports: [], aliases: [] ] } | |||
| ``` | |||
| ## Evaluation, Quoted & Macro expand | |||
| ### Evaluation | |||
| Return the evaluation result of the code from the file. | |||
| ``` | |||
| EVAL { :eval, 'path/to/file/which/holds/content/to/eval.tmp' } | |||
| ``` | |||
| ### Quoted | |||
| Return the code from the file quoted. | |||
| ``` | |||
| EVAL { :quote, 'path/to/file/which/holds/content/to/quote.tmp' } | |||
| ``` | |||
| ### Macro expand | |||
| Return the code from the file expanded. | |||
| ``` | |||
| EVAL { :expand, 'path/to/file/which/holds/content/to/expand.tmp' } | |||
| ``` | |||
| Return the code from the file expanded once. | |||
| ``` | |||
| EVAL { :expand_once, 'path/to/file/which/holds/content/to/expand_once.tmp' } | |||
| ``` | |||
| ## Definition lookup | |||
| Return the path to the source file which holds the definition. | |||
| ``` | |||
| DEFL { "List,flatten", [ context: Elixir, imports: [], aliases: [] ] } | |||
| DEFL { "nil,defmacro", [ context: Elixir, imports: [], aliases: [] ] } | |||
| DEFL { "nil,create_file", [ context: Elixir, imports: [Mix.Generator], aliases: [] ] } | |||
| DEFL { "MyList,nil", [ context: Elixir, imports: [], aliases: [{MyList, List}] ] } | |||
| ``` | |||
| ## Informations | |||
| ### Mix tasks | |||
| Return a list of all available mix tasks. | |||
| ``` | |||
| INFO { :type, :mixtasks } | |||
| ``` | |||
| ### Modules | |||
| Return a list of all available modules which has documentation. | |||
| ``` | |||
| INFO { :type, :modules } | |||
| ``` | |||
| ### Datatype Information | |||
| Return information about any datatype. | |||
| ``` | |||
| INFO { :type, :info, List } | |||
| ``` | |||
| ### Module Or Function/Arity Types Information | |||
| Return types for a module or function/arity pair. | |||
| ``` | |||
| INFO { :type, :types, 'List' } | |||
| INFO { :type, :types, 'Enum.t' } | |||
| INFO { :type, :types, 'Agent.on_start/0' } | |||
| ``` | |||
| ## End Markers | |||
| Each request type ends with a specific end marker tag to notify that the request is done. | |||
| An end tag looks like the following: | |||
| ``` | |||
| END-OF-<REQUEST TYPE> | |||
| ``` | |||
| For example, after the following request an end tag would look like this: | |||
| ``` | |||
| INFO { :type, :modules } | |||
| List | |||
| String | |||
| Enum | |||
| . | |||
| ... | |||
| .... | |||
| END-OF-INFO | |||
| ``` | |||
| @ -0,0 +1,41 @@ | |||
| Code.require_file "../helpers/complete.exs", __DIR__ | |||
| defmodule Alchemist.API.Comp do | |||
| @moduledoc false | |||
| alias Alchemist.Helpers.Complete | |||
| def request(args) do | |||
| args | |||
| |> normalize | |||
| |> process | |||
| end | |||
| def process([nil, _, imports, _]) do | |||
| Complete.run('', imports) ++ Complete.run('') | |||
| end | |||
| def process([hint, _context, imports, aliases]) do | |||
| Application.put_env(:"alchemist.el", :aliases, aliases) | |||
| Complete.run(hint, imports) ++ Complete.run(hint) | |||
| end | |||
| defp normalize(request) do | |||
| {{hint, [ context: context, | |||
| imports: imports, | |||
| aliases: aliases ]}, _} = Code.eval_string(request) | |||
| [hint, context, imports, aliases] | |||
| end | |||
| defp print(result) do | |||
| result | |||
| |> Enum.uniq | |||
| |> Enum.map(&IO.puts/1) | |||
| IO.puts "END-OF-COMP" | |||
| end | |||
| end | |||
| @ -0,0 +1,80 @@ | |||
| Code.require_file "../helpers/module_info.exs", __DIR__ | |||
| defmodule Alchemist.API.Defl do | |||
| @moduledoc false | |||
| alias Alchemist.Helpers.ModuleInfo | |||
| def request(args) do | |||
| args | |||
| |> normalize | |||
| |> process | |||
| |> IO.puts | |||
| IO.puts "END-OF-DEFL" | |||
| end | |||
| def process([nil, function, [context: _, imports: [], aliases: _]]) do | |||
| look_for_kernel_functions(function) | |||
| end | |||
| def process([nil, function, [context: _, imports: imports, aliases: _ ]]) do | |||
| module = Enum.filter(imports, &ModuleInfo.has_function?(&1, function)) | |||
| |> List.first | |||
| case module do | |||
| nil -> look_for_kernel_functions(function) | |||
| _ -> source(module) | |||
| end | |||
| end | |||
| def process([module, _function, [context: _, imports: _, aliases: aliases]]) do | |||
| if elixir_module?(module) do | |||
| module | |||
| |> Module.split | |||
| |> ModuleInfo.expand_alias(aliases) | |||
| else | |||
| module | |||
| end |> source | |||
| end | |||
| defp elixir_module?(module) do | |||
| module == Module.concat(Elixir, module) | |||
| end | |||
| defp look_for_kernel_functions(function) do | |||
| cond do | |||
| ModuleInfo.docs?(Kernel, function) -> | |||
| source(Kernel) | |||
| ModuleInfo.docs?(Kernel.SpecialForms, function) -> | |||
| source(Kernel.SpecialForms) | |||
| true -> "" | |||
| end | |||
| end | |||
| defp source([]), do: nil | |||
| defp source(module) when is_list(module) do | |||
| module | |||
| |> Module.concat | |||
| |> do_source | |||
| end | |||
| defp source(module), do: do_source(module) | |||
| defp do_source(module) do | |||
| if Code.ensure_loaded? module do | |||
| case module.module_info(:compile)[:source] do | |||
| nil -> nil | |||
| source -> List.to_string(source) | |||
| end | |||
| end | |||
| end | |||
| defp normalize(request) do | |||
| {{expr, context_info}, _} = Code.eval_string(request) | |||
| [module, function] = String.split(expr, ",", parts: 2) | |||
| {module, _} = Code.eval_string(module) | |||
| function = String.to_atom function | |||
| [module, function, context_info] | |||
| end | |||
| end | |||
| @ -0,0 +1,78 @@ | |||
| Code.require_file "../helpers/module_info.exs", __DIR__ | |||
| defmodule Alchemist.API.Docl do | |||
| @moduledoc false | |||
| import IEx.Helpers, warn: false | |||
| alias Alchemist.Helpers.ModuleInfo | |||
| def request(args) do | |||
| Application.put_env(:iex, :colors, [enabled: true]) | |||
| args | |||
| |> normalize | |||
| |> process | |||
| IO.puts "END-OF-DOCL" | |||
| end | |||
| def process([expr, modules, aliases]) do | |||
| search(expr, modules, aliases) | |||
| end | |||
| def search(nil), do: true | |||
| def search(expr) do | |||
| try do | |||
| Code.eval_string("h(#{expr})", [], __ENV__) | |||
| rescue | |||
| _e -> nil | |||
| end | |||
| end | |||
| def search(expr, modules, []) do | |||
| expr = to_string expr | |||
| unless function?(expr) do | |||
| search(expr) | |||
| else | |||
| search_with_context(modules, expr) | |||
| end | |||
| end | |||
| def search(expr, modules, aliases) do | |||
| unless function?(expr) do | |||
| String.split(expr, ".") | |||
| |> ModuleInfo.expand_alias(aliases) | |||
| |> search | |||
| else | |||
| search_with_context(modules, expr) | |||
| end | |||
| end | |||
| defp search_with_context(modules, expr) do | |||
| modules ++ [Kernel, Kernel.SpecialForms] | |||
| |> build_search(expr) | |||
| |> search | |||
| end | |||
| defp build_search(modules, search) do | |||
| function = Regex.replace(~r/\/[0-9]$/, search, "") | |||
| function = String.to_atom(function) | |||
| for module <- modules, | |||
| ModuleInfo.docs?(module, function) do | |||
| "#{module}.#{search}" | |||
| end |> List.first | |||
| end | |||
| defp function?(expr) do | |||
| Regex.match?(~r/^[a-z_]/, expr) | |||
| end | |||
| defp normalize(request) do | |||
| {{expr, [ context: _, | |||
| imports: imports, | |||
| aliases: aliases]}, _} = Code.eval_string(request) | |||
| [expr, imports, aliases] | |||
| end | |||
| end | |||
| @ -0,0 +1,63 @@ | |||
| defmodule Alchemist.API.Eval do | |||
| @moduledoc false | |||
| def request(args) do | |||
| args | |||
| |> normalize | |||
| |> process | |||
| IO.puts "END-OF-EVAL" | |||
| end | |||
| def process({:eval, file}) do | |||
| try do | |||
| File.read!("#{file}") | |||
| |> Code.eval_string | |||
| |> Tuple.to_list | |||
| |> List.first | |||
| |> IO.inspect | |||
| rescue | |||
| e -> IO.inspect e | |||
| end | |||
| end | |||
| def process({:quote, file}) do | |||
| try do | |||
| File.read!("#{file}") | |||
| |> Code.string_to_quoted | |||
| |> Tuple.to_list | |||
| |> List.last | |||
| |> IO.inspect | |||
| rescue | |||
| e -> IO.inspect e | |||
| end | |||
| end | |||
| def process({:expand, file}) do | |||
| try do | |||
| {_, expr} = File.read!("#{file}") | |||
| |> Code.string_to_quoted | |||
| res = Macro.expand(expr, __ENV__) | |||
| IO.puts Macro.to_string(res) | |||
| rescue | |||
| e -> IO.inspect e | |||
| end | |||
| end | |||
| def process({:expand_once, file}) do | |||
| try do | |||
| {_, expr} = File.read!("#{file}") | |||
| |> Code.string_to_quoted | |||
| res = Macro.expand_once(expr, __ENV__) | |||
| IO.puts Macro.to_string(res) | |||
| rescue | |||
| e -> IO.inspect e | |||
| end | |||
| end | |||
| def normalize(request) do | |||
| {expr , _} = Code.eval_string(request) | |||
| expr | |||
| end | |||
| end | |||
| @ -0,0 +1,86 @@ | |||
| Code.require_file "../helpers/module_info.exs", __DIR__ | |||
| Code.require_file "../helpers/complete.exs", __DIR__ | |||
| defmodule Alchemist.API.Info do | |||
| @moduledoc false | |||
| import IEx.Helpers, warn: false | |||
| alias Alchemist.Helpers.ModuleInfo | |||
| alias Alchemist.Helpers.Complete | |||
| def request(args) do | |||
| args | |||
| |> normalize | |||
| |> process | |||
| end | |||
| def process(:modules) do | |||
| modules = ModuleInfo.all_applications_modules | |||
| |> Enum.uniq | |||
| |> Enum.reject(&is_nil/1) | |||
| |> Enum.filter(&ModuleInfo.moduledoc?/1) | |||
| functions = Complete.run('') | |||
| modules ++ functions | |||
| |> Enum.uniq | |||
| |> Enum.map(&IO.puts/1) | |||
| IO.puts "END-OF-INFO" | |||
| end | |||
| def process(:mixtasks) do | |||
| # append things like hex or phoenix archives to the load_path | |||
| Mix.Local.append_archives | |||
| :code.get_path | |||
| |> Mix.Task.load_tasks | |||
| |> Enum.map(&Mix.Task.task_name/1) | |||
| |> Enum.sort | |||
| |> Enum.map(&IO.puts/1) | |||
| IO.puts "END-OF-INFO" | |||
| end | |||
| def process({:info, arg}) do | |||
| try do | |||
| Code.eval_string("i(#{arg})", [], __ENV__) | |||
| rescue | |||
| _e -> nil | |||
| end | |||
| IO.puts "END-OF-INFO" | |||
| end | |||
| def process({:types, arg}) do | |||
| try do | |||
| Code.eval_string("t(#{arg})", [], __ENV__) | |||
| rescue | |||
| _e -> nil | |||
| end | |||
| IO.puts "END-OF-INFO" | |||
| end | |||
| def process(nil) do | |||
| IO.puts "END-OF-INFO" | |||
| end | |||
| def normalize(request) do | |||
| try do | |||
| Code.eval_string(request) | |||
| rescue | |||
| _e -> nil | |||
| else | |||
| {{_, type }, _} -> type | |||
| {{_, type, arg}, _} -> | |||
| if Version.match?(System.version, ">=1.2.0-rc") do | |||
| {type, arg} | |||
| else | |||
| nil | |||
| end | |||
| end | |||
| end | |||
| end | |||
| @ -0,0 +1,351 @@ | |||
| Code.require_file "module_info.exs", __DIR__ | |||
| defmodule Alchemist.Helpers.Complete do | |||
| alias Alchemist.Helpers.ModuleInfo | |||
| @moduledoc """ | |||
| This Alchemist.Completer holds a codebase copy of the | |||
| IEx.Autocomplete because for the use of context specific | |||
| aliases. | |||
| With the release of Elixir v1.1 the IEx.Autocomplete will | |||
| look for aliases in a certain environment variable | |||
| `Application.get_env(:iex, :autocomplete_server)` and until | |||
| then we'll use our own autocomplete codebase. | |||
| """ | |||
| 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 run(hint, modules) do | |||
| for module <- modules do | |||
| ModuleInfo.get_functions(module, hint) | |||
| end |> List.flatten | |||
| 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 | |||
| Application.get_env(:"alchemist.el", :aliases) | |||
| |> format_aliases | |||
| end | |||
| defp format_aliases(nil), do: [] | |||
| defp format_aliases(list), do: list | |||
| 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,117 @@ | |||
| defmodule Alchemist.Helpers.ModuleInfo do | |||
| @moduledoc false | |||
| def moduledoc?(module) do | |||
| case Code.get_docs module, :moduledoc do | |||
| {_, doc} -> is_binary doc | |||
| _ -> false | |||
| end | |||
| end | |||
| def docs?(module, function) do | |||
| docs = Code.get_docs module, :docs | |||
| do_docs?(docs, function) | |||
| end | |||
| def expand_alias([name | rest] = list, aliases) do | |||
| module = Module.concat(Elixir, name) | |||
| Enum.find_value(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) |> normalize_module | |||
| end | |||
| def get_functions(module, hint) do | |||
| hint = to_string hint | |||
| {module, _} = Code.eval_string(module) | |||
| functions = get_module_funs(module) | |||
| list = Enum.reduce functions, [], 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 | |||
| do_get_functions(list, hint) |> :lists.sort() | |||
| end | |||
| def has_function?(module, function) do | |||
| List.keymember? get_module_funs(module), function, 0 | |||
| end | |||
| defp do_get_functions(list, "") do | |||
| all_functions(list) | |||
| end | |||
| defp do_get_functions(list, hint) do | |||
| all_functions(list, hint) | |||
| end | |||
| defp get_module_funs(module) do | |||
| case Code.ensure_loaded(module) do | |||
| {:module, _} -> | |||
| module.module_info(:functions) ++ module.__info__(:macros) | |||
| _otherwise -> | |||
| [] | |||
| end | |||
| end | |||
| defp all_functions(list) do | |||
| for {fun, arities} <- list, name = Atom.to_string(fun) do | |||
| "#{name}/#{List.first(arities)}" | |||
| end | |||
| end | |||
| defp all_functions(list, hint) do | |||
| for {fun, arities} <- list, | |||
| name = Atom.to_string(fun), | |||
| String.starts_with?(name, hint) do | |||
| "#{name}/#{List.first(arities)}" | |||
| end | |||
| end | |||
| def all_applications_modules do | |||
| for [app] <- loaded_applications(), | |||
| {:ok, modules} = :application.get_key(app, :modules), | |||
| module <- modules do | |||
| module | |||
| end | |||
| end | |||
| defp do_docs?([head|tail], function) do | |||
| {{func, _}, _, _, _, doc} = head | |||
| if func == function and is_binary(doc) do | |||
| true | |||
| else | |||
| do_docs?(tail, function) | |||
| end | |||
| end | |||
| defp do_docs?([], _function), do: false | |||
| defp do_docs?(nil, _function), do: false | |||
| 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 normalize_module(mod) do | |||
| if is_list(mod) do | |||
| Module.concat(mod) | |||
| else | |||
| mod | |||
| end | |||
| end | |||
| end | |||
| @ -0,0 +1,93 @@ | |||
| Code.require_file "api/comp.exs", __DIR__ | |||
| Code.require_file "api/docl.exs", __DIR__ | |||
| Code.require_file "api/defl.exs", __DIR__ | |||
| Code.require_file "api/eval.exs", __DIR__ | |||
| Code.require_file "api/info.exs", __DIR__ | |||
| defmodule Alchemist.Server do | |||
| @version "0.1.0-beta" | |||
| @moduledoc """ | |||
| The Alchemist-Server operates as an informant for a specific desired | |||
| Elixir Mix project and serves with informations as the following: | |||
| * Completion for Modules and functions. | |||
| * Documentation lookup for Modules and functions. | |||
| * Code evaluation and quoted representation of code. | |||
| * Definition lookup of code. | |||
| * Listing of all available Mix tasks. | |||
| * Listing of all available Modules with documentation. | |||
| """ | |||
| alias Alchemist.API | |||
| def start([env]) do | |||
| 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) | |||
| loop(loaded, env) | |||
| end | |||
| def read_input(line) do | |||
| case line |> String.split(" ", parts: 2) do | |||
| ["COMP", args] -> | |||
| API.Comp.request(args) | |||
| ["DOCL", args] -> | |||
| API.Docl.request(args) | |||
| ["INFO", args] -> | |||
| API.Info.request(args) | |||
| ["EVAL", args] -> | |||
| API.Eval.request(args) | |||
| ["DEFL", args] -> | |||
| API.Defl.request(args) | |||
| _ -> | |||
| 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 | |||
| @ -0,0 +1,3 @@ | |||
| Code.require_file "lib/server.exs", __DIR__ | |||
| Alchemist.Server.start([System.argv]) | |||
| @ -0,0 +1,57 @@ | |||
| Code.require_file "../test_helper.exs", __DIR__ | |||
| Code.require_file "../../lib/api/comp.exs", __DIR__ | |||
| defmodule Alchemist.API.CompTest do | |||
| use ExUnit.Case, async: true | |||
| import ExUnit.CaptureIO | |||
| alias Alchemist.API.Comp | |||
| test "COMP request with empty hint" do | |||
| assert capture_io(fn -> | |||
| Comp.process([nil, Elixir, [], [] ]) | |||
| end) =~ """ | |||
| import/2 | |||
| quote/2 | |||
| require/2 | |||
| END-OF-COMP | |||
| """ | |||
| end | |||
| test "COMP request without empty hint" do | |||
| assert capture_io(fn -> | |||
| Comp.process(['is_b', Elixir, [], []]) | |||
| end) =~ """ | |||
| is_b | |||
| is_binary/1 | |||
| is_bitstring/1 | |||
| is_boolean/1 | |||
| END-OF-COMP | |||
| """ | |||
| end | |||
| test "COMP request with an alias" do | |||
| assert capture_io(fn -> | |||
| Comp.process(['MyList.flat', Elixir, [], [{MyList, List}]]) | |||
| end) =~ """ | |||
| MyList.flatten | |||
| flatten/1 | |||
| flatten/2 | |||
| END-OF-COMP | |||
| """ | |||
| end | |||
| test "COMP request with a module hint" do | |||
| assert capture_io(fn -> | |||
| Comp.process(['Str', Elixir, [], []]) | |||
| end) =~ """ | |||
| Str | |||
| Stream | |||
| String | |||
| StringIO | |||
| END-OF-COMP | |||
| """ | |||
| end | |||
| end | |||
| @ -0,0 +1,45 @@ | |||
| Code.require_file "../test_helper.exs", __DIR__ | |||
| Code.require_file "../../lib/api/defl.exs", __DIR__ | |||
| defmodule Alchemist.API.DeflTest do | |||
| use ExUnit.Case | |||
| alias Alchemist.API.Defl | |||
| test "DEFL request call for defmodule" do | |||
| context = [context: Elixir, imports: [], aliases: []] | |||
| assert Defl.process([nil, :defmodule, context]) =~ "lib/elixir/lib/kernel.ex" | |||
| end | |||
| test "DEFL request call for import" do | |||
| context = [context: Elixir, imports: [], aliases: []] | |||
| assert Defl.process([nil, :import, context]) =~ "lib/elixir/lib/kernel/special_forms.ex" | |||
| end | |||
| test "DEFL request call for create_file with available import" do | |||
| context = [context: Elixir, imports: [Mix.Generator], aliases: []] | |||
| assert Defl.process([nil, :create_file, context]) =~ "lib/mix/lib/mix/generator.ex" | |||
| end | |||
| test "DEFL request call for MyList.flatten with available aliases" do | |||
| context = [context: Elixir, imports: [], aliases: [{MyList, List}]] | |||
| assert Defl.process([MyList, :flatten, context]) =~ "lib/elixir/lib/list.ex" | |||
| end | |||
| test "DEFL request call for String module" do | |||
| context = [context: Elixir, imports: [], aliases: []] | |||
| assert Defl.process([String, nil, context]) =~ "lib/elixir/lib/string.ex" | |||
| end | |||
| test "DEFL request call for erlang module" do | |||
| context = [ context: Elixir, imports: [], aliases: [] ] | |||
| assert Defl.process([:lists, :duplicate, context]) =~ "lib/stdlib/src/lists.erl" | |||
| end | |||
| test "DEFL request call for none existing module" do | |||
| context = [ context: Elixir, imports: [], aliases: [] ] | |||
| assert Defl.process([Rock, :duplicate, context]) == nil | |||
| end | |||
| end | |||
| @ -0,0 +1,73 @@ | |||
| Code.require_file "../test_helper.exs", __DIR__ | |||
| Code.require_file "../../lib/api/comp.exs", __DIR__ | |||
| Code.require_file "../../lib/api/docl.exs", __DIR__ | |||
| defmodule Alchemist.API.DoclTest do | |||
| use ExUnit.Case, async: true | |||
| import ExUnit.CaptureIO | |||
| alias Alchemist.API.Docl | |||
| test "DOCL request" do | |||
| assert capture_io(fn -> | |||
| Docl.process(['defmodule', [], []]) | |||
| end) =~ """ | |||
| Defines a module given by name with the given contents. | |||
| """ | |||
| end | |||
| test "DOCL request for List.flatten" do | |||
| assert capture_io(fn -> | |||
| Docl.process(["List.flatten", [], []]) | |||
| end) =~ """ | |||
| Flattens the given \e[36mlist\e[0m of nested lists. | |||
| \e[0m | |||
| \e[33mExamples\e[0m | |||
| \e[0m | |||
| \e[36m\e[1m┃ iex> List.flatten([1, [[2], 3]]) | |||
| """ | |||
| end | |||
| test "DOCL request for MyCustomList.flatten with alias" do | |||
| assert capture_io(fn -> | |||
| Docl.process(["MyCustomList.flatten", [], [{MyCustomList, List}]]) | |||
| end) =~ """ | |||
| Flattens the given \e[36mlist\e[0m of nested lists. | |||
| \e[0m | |||
| \e[33mExamples\e[0m | |||
| \e[0m | |||
| \e[36m\e[1m┃ iex> List.flatten([1, [[2], 3]]) | |||
| """ | |||
| end | |||
| test "DOCL request for search create_file with import" do | |||
| assert capture_io(fn -> | |||
| Docl.process(["create_file", [Mix.Generator], []]) | |||
| end) =~ """ | |||
| def create_file(path, contents, opts \\\\ []) \e[0m | |||
| \e[0m | |||
| Creates a file with the given contents. If the file already exists, asks for | |||
| user confirmation. | |||
| \e[0m | |||
| """ | |||
| end | |||
| test "DOCL request for defmacro" do | |||
| assert capture_io(fn -> | |||
| Docl.process(["defmacro", [], []]) | |||
| end) =~ """ | |||
| \e[7m\e[33m defmacro defmacro(call, expr \\\\ nil) \e[0m | |||
| """ | |||
| end | |||
| test "DOCL request for Path.basename/1" do | |||
| assert capture_io(fn -> | |||
| Docl.process(["Path.basename/1", [], []]) | |||
| end) =~ """ | |||
| Returns the last component of the path or the path itself if it does not | |||
| contain any directory separators. | |||
| """ | |||
| end | |||
| end | |||
| @ -0,0 +1,19 @@ | |||
| Code.require_file "test_helper.exs", __DIR__ | |||
| Code.require_file "../lib/api/comp.exs", __DIR__ | |||
| Code.require_file "../lib/api/docl.exs", __DIR__ | |||
| defmodule APITest do | |||
| use ExUnit.Case, async: true | |||
| import ExUnit.CaptureIO | |||
| alias Alchemist.API | |||
| test "DOCL request" do | |||
| assert capture_io(fn -> | |||
| API.Docl.process(['defmodule', [], []]) | |||
| end) =~ """ | |||
| Defines a module given by name with the given contents. | |||
| """ | |||
| end | |||
| end | |||
| @ -0,0 +1,41 @@ | |||
| Code.require_file "../test_helper.exs", __DIR__ | |||
| Code.require_file "../../lib/helpers/complete.exs", __DIR__ | |||
| defmodule CompleteTest do | |||
| use ExUnit.Case, async: true | |||
| import Alchemist.Helpers.Complete | |||
| defmodule MyModule do | |||
| def say_hi, do: true | |||
| end | |||
| test "return completion candidates for 'List'" do | |||
| assert run('List') == ['List.', 'Chars', 'first/1', 'last/1', 'to_atom/1', | |||
| 'to_existing_atom/1', 'to_float/1', 'to_string/1', 'to_tuple/1', | |||
| 'wrap/1', 'zip/1', 'delete/2', 'delete_at/2', 'duplicate/2', | |||
| 'keysort/2', 'flatten/1', 'flatten/2', 'to_integer/1', | |||
| 'to_integer/2', 'foldl/3', 'foldr/3', 'insert_at/3', 'keydelete/3', | |||
| 'keymember?/3', 'keytake/3', 'replace_at/3', 'update_at/3', | |||
| 'keyfind/4', 'keyreplace/4', 'keystore/4'] | |||
| end | |||
| test "return completion candidates for 'Str'" do | |||
| assert run('Str') == ['Str', 'Stream', 'String', 'StringIO'] | |||
| end | |||
| test "return completion candidates for 'List.del'" do | |||
| assert run('List.del') == ['List.delete', 'delete/2', 'delete_at/2'] | |||
| end | |||
| test "return completion candidates for module with alias" do | |||
| Application.put_env(:"alchemist.el", :aliases, [{MyList, List}]) | |||
| assert run('MyList.del') == ['MyList.delete', 'delete/2', 'delete_at/2'] | |||
| end | |||
| test "return completion candidates for functions from import" do | |||
| imports = [MyModule] | |||
| assert run('say', imports) == ["say_hi/0"] | |||
| end | |||
| end | |||
| @ -0,0 +1,45 @@ | |||
| Code.require_file "../test_helper.exs", __DIR__ | |||
| Code.require_file "../../lib/helpers/module_info.exs", __DIR__ | |||
| defmodule Alchemist.Helpers.ModuleTest do | |||
| use ExUnit.Case | |||
| alias Alchemist.Helpers.ModuleInfo | |||
| test "moduledoc? returns true" do | |||
| assert ModuleInfo.moduledoc?(List) == true | |||
| end | |||
| test "moduledoc? returns false" do | |||
| assert ModuleInfo.moduledoc?(List.Chars.Atom) == false | |||
| end | |||
| test "docs? returns true" do | |||
| assert ModuleInfo.docs?(List, :flatten) == true | |||
| assert ModuleInfo.docs?(Kernel, :def) == true | |||
| end | |||
| test "docs? returns false" do | |||
| assert ModuleInfo.docs?(List, :dance) == false | |||
| assert ModuleInfo.docs?(nil, :dance) == false | |||
| end | |||
| test "expand_alias return expanded module alias" do | |||
| aliases = [{MyList, List}, {MyGenServer, :gen_server}] | |||
| assert ModuleInfo.expand_alias([MyList], aliases) == List | |||
| assert ModuleInfo.expand_alias([MyGenServer], aliases) == :gen_server | |||
| assert ModuleInfo.expand_alias([MyList], aliases) == List | |||
| end | |||
| test "has_function? return true" do | |||
| assert ModuleInfo.has_function?(List, :flatten) == true | |||
| assert ModuleInfo.has_function?(List, :to_string) == true | |||
| end | |||
| test "has_function? return false" do | |||
| assert ModuleInfo.has_function?(List, :split) == false | |||
| assert ModuleInfo.has_function?(List, :map) == false | |||
| end | |||
| end | |||
| @ -0,0 +1,126 @@ | |||
| Code.require_file "test_helper.exs", __DIR__ | |||
| Code.require_file "../lib/server.exs", __DIR__ | |||
| defmodule ServerTest do | |||
| use ExUnit.Case | |||
| import ExUnit.CaptureIO | |||
| setup_all do | |||
| on_exit fn -> | |||
| {_status, files} = File.ls Path.expand("fixtures", __DIR__) | |||
| files |> Enum.each(fn(file) -> | |||
| unless file == ".gitkeep" do | |||
| File.rm Path.expand("fixtures/#{file}", __DIR__) | |||
| end | |||
| end) | |||
| end | |||
| end | |||
| test "Expression completion" do | |||
| assert send_signal("COMP { 'def', [context: Elixir, imports: [], aliases: []]}") =~ """ | |||
| defoverridable/1 | |||
| """ | |||
| end | |||
| test "Documentation lookup" do | |||
| assert send_signal("DOCL { 'List', [context: Elixir, imports: [], aliases: []]}") =~ """ | |||
| \e[0m\n\e[7m\e[33m List \e[0m\n\e[0m | |||
| """ | |||
| end | |||
| test "Getting the definition source file information of code" do | |||
| assert send_signal("DEFL {\"List,delete\", [context: Elixir, imports: [], aliases: []]}") =~ "/lib/elixir/lib/list.ex" | |||
| end | |||
| test "Evaluate the content of a file" do | |||
| filename = Path.expand("fixtures/eval_fixture.exs", __DIR__) | |||
| File.write(filename, "1+1") | |||
| assert send_signal("EVAL { :eval, '#{filename}' }") =~ "2" | |||
| end | |||
| test "Evaluate and quote the content of a file" do | |||
| filename = Path.expand("fixtures/eval_and_quote_fixture.exs", __DIR__) | |||
| File.write(filename, "[4,2,1,3] |> Enum.sort") | |||
| assert send_signal("EVAL { :quote, '#{filename}' }") =~ """ | |||
| {{:., [line: 1], [{:__aliases__, [counter: 0, line: 1], [:Enum]}, :sort]},\n [line: 1], []}]} | |||
| """ | |||
| end | |||
| test "Expand macro once" do | |||
| filename = Path.expand("fixtures/macro_expand_once_fixture.exs", __DIR__) | |||
| File.write(filename, "unless true, do: IO.puts \"this should never be printed\"") | |||
| assert send_signal("EVAL { :expand_once, '#{filename}' }") =~ """ | |||
| if(true) do | |||
| nil | |||
| else | |||
| IO.puts("this should never be printed") | |||
| end | |||
| """ | |||
| end | |||
| test "Expand macro" do | |||
| filename = Path.expand("fixtures/macro_expand_fixture.exs", __DIR__) | |||
| File.write(filename, "unless true, do: IO.puts \"this should never be printed\"") | |||
| assert send_signal("EVAL { :expand, '#{filename}' }") =~ """ | |||
| case(true) do | |||
| x when x in [false, nil] -> | |||
| IO.puts("this should never be printed") | |||
| _ -> | |||
| nil | |||
| end | |||
| """ | |||
| end | |||
| test "Get all available application modules" do | |||
| assert send_signal("INFO { :type, :modules }") =~ """ | |||
| Elixir.Logger | |||
| Elixir.Logger.Formatter | |||
| Elixir.Logger.Translator | |||
| """ | |||
| end | |||
| test "Get all available mix tasks by name" do | |||
| assert send_signal("INFO { :type, :mixtasks }") =~ """ | |||
| app.start | |||
| archive | |||
| archive.build | |||
| archive.install | |||
| archive.uninstall | |||
| clean | |||
| cmd | |||
| compile | |||
| """ | |||
| end | |||
| # The IEx.Helpers.t and IEx.Helpers.i are functionality which come with | |||
| # Elixir version 1.2.0 | |||
| if Version.match?(System.version, ">=1.2.0-rc") do | |||
| test "Get information from data type" do | |||
| assert send_signal("INFO { :type, :info, List}") =~ """ | |||
| Reference modules\e[0m\n\e[22m Module, Atom\e[0m\nEND-OF-INFO | |||
| """ | |||
| end | |||
| test "Don't crash server if data type argument is faulty" do | |||
| assert send_signal("INFO { :type, :info, whatever}") =~ """ | |||
| END-OF-INFO | |||
| """ | |||
| end | |||
| test "Prints the types for the given module or for the given function/arity pair" do | |||
| assert send_signal("INFO { :type, :types, 'Agent'}") =~ """ | |||
| @type agent() :: pid() | {atom(), node()} | name()\e[0m\n\e[22m@type state() :: term()\e[0m\nEND-OF-INFO | |||
| """ | |||
| assert send_signal("INFO { :type, :types, 'Agent.on_start/0'}") =~ """ | |||
| @type on_start() :: {:ok, pid()} | {:error, {:already_started, pid()} | term()}\e[0m | |||
| """ | |||
| end | |||
| end | |||
| defp send_signal(signal) do | |||
| capture_io(fn -> | |||
| Alchemist.Server.read_input(signal) | |||
| end) | |||
| end | |||
| end | |||
| @ -0,0 +1 @@ | |||
| ExUnit.start() | |||
| @ -0,0 +1,373 @@ | |||
| ;;; 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: | |||
| (require 'dash) | |||
| (require 'alchemist-project) | |||
| (defgroup alchemist-test-mode nil | |||
| "Minor mode for Elixir ExUnit files." | |||
| :prefix "alchemist-test-mode-" | |||
| :group 'alchemist) | |||
| ;; Variables | |||
| (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) | |||
| (defcustom alchemist-test-display-compilation-output nil | |||
| "if Non-nil, compilation informations will be displayed | |||
| in the test report buffer." | |||
| :type 'boolean | |||
| :group 'alchemist-test-mode) | |||
| (defcustom alchemist-test-status-modeline t | |||
| "if Non-nil, the face of local `mode-name' variable will change with test run status. | |||
| For example, when `alchemist-mix-test' fails, the `mode-name' will be | |||
| formatted with the `alchemist-test--failed-face' face, to symbolize failing tests." | |||
| :type 'boolean | |||
| :group 'alchemist-test) | |||
| (defcustom alchemist-test-ask-about-save t | |||
| "Non-nil means 'alchemist-test-excute` asks which buffers to save before running. | |||
| Otherwise, it saves all modified buffers without asking." | |||
| :type 'boolean | |||
| :group 'alchemist-test) | |||
| (defvar alchemist-test--last-run-status "") | |||
| (defconst alchemist-test-report-buffer-name "*alchemist test report*" | |||
| "Name of the test report buffer.") | |||
| (defconst alchemist-test-report-process-name "alchemist-test-process" | |||
| "Name of the test report process.") | |||
| (defconst alchemist-test--failing-files-regex | |||
| "\\( [0-9]+).+\n\s+\\)\\([-A-Za-z0-9./_]+:[0-9]+\\)$") | |||
| (defconst alchemist-test--stacktrace-files-regex | |||
| "\\( \\)\\([-A-Za-z0-9./_]+:[0-9]+\\).*") | |||
| ;; Faces | |||
| (defface alchemist-test--test-file-and-location-face | |||
| '((t (:inherit font-lock-variable-name-face :weight bold))) | |||
| "Face for the file where the failed test are." | |||
| :group 'alchemist-test) | |||
| (defface alchemist-test--stacktrace-file-and-location-face | |||
| '((t (:inherit font-lock-keyword-face :weight bold))) | |||
| "Face for the stacktrace files." | |||
| :group 'alchemist-test) | |||
| (defface alchemist-test--success-face | |||
| '((t (:inherit font-lock-variable-name-face :bold t :background "darkgreen" :foreground "white"))) | |||
| "Face for successful compilation run." | |||
| :group 'alchemist-test) | |||
| (defface alchemist-test--failed-face | |||
| '((t (:inherit font-lock-variable-name-face :bold t :background "red" :foreground "white"))) | |||
| "Face for failed compilation run." | |||
| :group 'alchemist-test) | |||
| (defvar alchemist-test--mode-name-face 'mode-line) | |||
| (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-report-mode-map | |||
| (let ((map (make-sparse-keymap))) | |||
| (define-key map "q" #'quit-window) | |||
| (define-key map "t" #'toggle-truncate-lines) | |||
| (define-key map "r" #'alchemist-mix-rerun-last-test) | |||
| (define-key map (kbd "M-n") #'alchemist-test-next-result) | |||
| (define-key map (kbd "M-p") #'alchemist-test-previous-result) | |||
| (define-key map (kbd "M-N") #'alchemist-test-next-stacktrace-file) | |||
| (define-key map (kbd "M-P") #'alchemist-test-previous-stacktrace-file) | |||
| (define-key map (kbd "C-c C-k") #'alchemist-report-interrupt-current-process) | |||
| map)) | |||
| (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'.") | |||
| (defconst alchemist-test-mode--test-regex | |||
| (let ((whitespace-opt "[[:space:]]*") | |||
| (whitespace "[[:space:]]+")) | |||
| (concat "\\(^" whitespace-opt "test" whitespace "\\(?10:.+\\)" whitespace "do" whitespace-opt "$" | |||
| "\\|" | |||
| whitespace " [0-9]+) test .+\\)"))) | |||
| ;; Private functions | |||
| (defun alchemist-test--set-modeline-color (status) | |||
| (setq alchemist-test--mode-name-face | |||
| (if (string-prefix-p "finished" status) | |||
| 'alchemist-test--success-face | |||
| 'alchemist-test--failed-face))) | |||
| (defun alchemist-test--render-report (buffer) | |||
| (with-current-buffer buffer | |||
| (let ((inhibit-read-only t)) | |||
| (alchemist-test--render-files)))) | |||
| (defun alchemist-test--render-files () | |||
| (alchemist-test--render-test-failing-files) | |||
| (alchemist-test--render-stacktrace-files)) | |||
| (defun alchemist-test--render-test-failing-files () | |||
| (alchemist-test--render-file alchemist-test--failing-files-regex | |||
| 'alchemist-test--test-file-and-location-face)) | |||
| (defun alchemist-test--render-stacktrace-files () | |||
| (alchemist-test--render-file alchemist-test--stacktrace-files-regex | |||
| 'alchemist-test--stacktrace-file-and-location-face)) | |||
| (defun alchemist-test--render-file (regex face) | |||
| (save-excursion | |||
| (goto-char (point-min)) | |||
| (while (re-search-forward regex nil t) | |||
| (let ((file (buffer-substring-no-properties (match-beginning 2) (match-end 2)))) | |||
| (goto-char (match-beginning 2)) | |||
| (replace-match "" nil nil nil 2) | |||
| (insert-text-button file | |||
| 'face face | |||
| 'file file | |||
| 'follow-link t | |||
| 'action #'alchemist-test--open-file | |||
| 'help-echo "visit the source location"))))) | |||
| (defun alchemist-test--open-file (button) | |||
| (save-match-data | |||
| (string-match "\\([-A-Za-z0-9./_]+\\):\\([0-9]+\\)" (button-get button 'file)) | |||
| (let* ((file-with-line (button-get button 'file)) | |||
| (file (substring-no-properties file-with-line (match-beginning 1) (match-end 1))) | |||
| (line (string-to-number (substring-no-properties file-with-line (match-beginning 2) (match-end 2)))) | |||
| (file-path (if (file-exists-p file) | |||
| file | |||
| (expand-file-name (concat (alchemist-project-root) file))))) | |||
| (with-current-buffer (find-file-other-window file-path) | |||
| (goto-char (point-min)) | |||
| (forward-line (- line 1)))))) | |||
| (defun alchemist-test--handle-exit (status buffer) | |||
| (when alchemist-test-status-modeline | |||
| (alchemist-test--set-modeline-color status)) | |||
| (with-current-buffer buffer | |||
| (let ((inhibit-read-only t)) | |||
| (alchemist-test--render-files)))) | |||
| (defun alchemist-test-mode--buffer-contains-tests-p () | |||
| "Return nil if the current buffer contains no tests, non-nil if it does." | |||
| (alchemist-utils-occur-in-buffer-p (current-buffer) alchemist-test-mode--test-regex)) | |||
| (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 | |||
| (goto-char (point-min)) | |||
| (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)))) | |||
| (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]*\\|flunk\\)\s+" 1 | |||
| font-lock-type-face t) | |||
| ("^\s+\\(assert[_a-z]*\\|refute[_a-z]*\\|flunk\\)\(" 1 | |||
| font-lock-type-face t))))) | |||
| ;; Public functions | |||
| (define-derived-mode alchemist-test-report-mode fundamental-mode "Alchemist Test Report" | |||
| "Major mode for presenting Elixir test results. | |||
| \\{alchemist-test-report-mode-map}" | |||
| (setq buffer-read-only t) | |||
| (setq-local truncate-lines t) | |||
| (setq-local electric-indent-chars nil)) | |||
| (defun alchemist-test-save-buffers () | |||
| "Save some modified file-visiting buffers." | |||
| (save-some-buffers (not alchemist-test-ask-about-save) nil)) | |||
| (defun alchemist-test-clean-compilation-output (output) | |||
| (if (not alchemist-test-display-compilation-output) | |||
| (with-temp-buffer | |||
| (insert output) | |||
| (delete-matching-lines "^Compiled .+" (point-min) (point-max)) | |||
| (delete-matching-lines "^Generated .+" (point-min) (point-max)) | |||
| (buffer-substring-no-properties (point-min) (point-max))) | |||
| output)) | |||
| (defun alchemist-test-execute (command-list) | |||
| (message "Testing...") | |||
| (let* ((command (mapconcat 'concat (-flatten command-list) " "))) | |||
| (alchemist-test-save-buffers) | |||
| (alchemist-report-run command | |||
| alchemist-test-report-process-name | |||
| alchemist-test-report-buffer-name | |||
| 'alchemist-test-report-mode | |||
| #'alchemist-test--handle-exit))) | |||
| (defun alchemist-test-initialize-modeline () | |||
| "Initialize the mode-line face." | |||
| (when alchemist-test-status-modeline | |||
| (setq mode-name | |||
| '(:eval (propertize "Elixir" 'face alchemist-test--mode-name-face))))) | |||
| (defun alchemist-test-reset-modeline () | |||
| "Reset the current mode-line face to default." | |||
| (setq mode-name "Elixir")) | |||
| (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-utils-jump-to-next-matching-line alchemist-test-mode--test-regex 'back-to-indentation)) | |||
| (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-utils-jump-to-previous-matching-line alchemist-test-mode--test-regex 'back-to-indentation)) | |||
| (defun alchemist-test-next-result () | |||
| "Jump to the next error in the test report. | |||
| If there are no error after the current position, | |||
| jump to the first error in the test report. | |||
| Do nothing if there are no error in this test report." | |||
| (interactive) | |||
| (alchemist-utils-jump-to-next-matching-line alchemist-test--failing-files-regex | |||
| 'back-to-indentation)) | |||
| (defun alchemist-test-previous-result () | |||
| "Jump to the previous error in the test report. | |||
| If there are no error before the current position, | |||
| jump to the first error in the test report. | |||
| Do nothing if there are no error in this test report." | |||
| (interactive) | |||
| (alchemist-utils-jump-to-previous-matching-line alchemist-test--failing-files-regex | |||
| #'(lambda () | |||
| (forward-line 1) | |||
| (back-to-indentation)))) | |||
| (defun alchemist-test-next-stacktrace-file () | |||
| "Jump to the next stacktrace file in the test report. | |||
| If there are no stacktrace file after the current position, | |||
| jump to the first stacktrace file in the test report. | |||
| Do nothing if there are no stacktrace file in this test report." | |||
| (interactive) | |||
| (alchemist-utils-jump-to-next-matching-line alchemist-test--stacktrace-files-regex | |||
| 'back-to-indentation)) | |||
| (defun alchemist-test-previous-stacktrace-file () | |||
| "Jump to the previous stacktrace file in the test report. | |||
| If there are no stacktrace file before the current position, | |||
| jump to the first stacktrace file in the test report. | |||
| Do nothing if there are no stacktrace file in this test report." | |||
| (interactive) | |||
| (alchemist-utils-jump-to-previous-matching-line alchemist-test--stacktrace-files-regex | |||
| 'back-to-indentation)) | |||
| (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-toggle-test-report-display () | |||
| "Toggle between display or hidding `alchemist-test-report-buffer-name' buffer." | |||
| (interactive) | |||
| (let* ((buffer (get-buffer alchemist-test-report-buffer-name)) | |||
| (window (get-buffer-window buffer))) | |||
| (if buffer | |||
| (if window | |||
| (quit-window nil window) | |||
| (display-buffer buffer)) | |||
| (message "No Alchemist test report buffer exists.")))) | |||
| ;;;###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-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,162 @@ | |||
| ;;; alchemist-utils.el --- Common utility functions that don't belong anywhere else -*- 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: | |||
| ;; Common utility functions that don't belong anywhere else | |||
| ;;; Code: | |||
| (require 'cl-lib) | |||
| (require 'dash) | |||
| (defface alchemist-utils--deprecated-face | |||
| '((t (:inherit font-lock-variable-name-face :bold t :foreground "red"))) | |||
| "Face for 'deprecated' word inside deprecated message." | |||
| :group 'alchemist) | |||
| (defun alchemist-utils-deprecated-message (function new-function) | |||
| (message "'%s is %s in favor of '%s" | |||
| function (propertize "deprecated" | |||
| 'face 'alchemist-utils--deprecated-face) | |||
| new-function)) | |||
| (defun alchemist-utils-build-command (command-list) | |||
| "Build the commands list for the runner." | |||
| (let* ((command-list (-flatten (if (stringp command-list) | |||
| (split-string command-list) | |||
| command-list))) | |||
| (command (-remove (lambda (e) (equal e "")) command-list))) | |||
| (mapconcat 'concat command " "))) | |||
| (defun alchemist-utils-count-char-occurence (regexp str) | |||
| "Count occurrence of char with REGEXP inside STR." | |||
| (cl-loop with start = 0 | |||
| for count from 0 | |||
| while (string-match regexp str start) | |||
| do (setq start (match-end 0)) | |||
| finally return count)) | |||
| (defun alchemist-utils-test-file-p () | |||
| "Return non-nil `current-buffer' holds an Elixir test file." | |||
| (string-match "_test\\.exs$" (or (buffer-file-name) ""))) | |||
| (defun alchemist-utils-remove-dot-at-the-end (string) | |||
| "Remove dot character at the end of STRING." | |||
| (replace-regexp-in-string "\\.$" "" string)) | |||
| (defun alchemist-utils-empty-string-p (string) | |||
| "Return non-nil if STRING is null, blank or whitespace only." | |||
| (or (null string) | |||
| (string= string "") | |||
| (if (string-match-p "^\s+$" string) t))) | |||
| (defun alchemist-utils-prepare-aliases-for-elixir (aliases) | |||
| (let* ((aliases (-map (lambda (a) | |||
| (let ((module (alchemist-utils-remove-dot-at-the-end (car a))) | |||
| (alias (alchemist-utils-remove-dot-at-the-end (car (cdr a))))) | |||
| (if (not (or (alchemist-utils-empty-string-p alias) | |||
| (string= alias module))) | |||
| (format "{%s, %s}" | |||
| (if (alchemist-utils-empty-string-p alias) | |||
| module | |||
| alias) | |||
| module)))) aliases)) | |||
| (aliases (mapconcat #'identity aliases ","))) | |||
| (format "[%s]" aliases))) | |||
| (defun alchemist-utils-prepare-modules-for-elixir (modules) | |||
| (let* ((modules (mapconcat #'identity modules ","))) | |||
| (format "[%s]" modules))) | |||
| (defun alchemist-utils--snakecase-to-camelcase (str) | |||
| "Convert a snake_case string STR to a CamelCase string. | |||
| This function is useful for converting file names like my_module to Elixir | |||
| module names (MyModule)." | |||
| (mapconcat 'capitalize (split-string str "_") "")) | |||
| (defun alchemist-utils-add-ext-to-path-if-not-present (path ext) | |||
| "Add EXT to PATH if PATH doesn't already ends with EXT." | |||
| (if (string-suffix-p ext path) | |||
| path | |||
| (concat path ext))) | |||
| (defun alchemist-utils-path-to-module-name (path) | |||
| "Convert PATH to its Elixir module name equivalent. | |||
| For example, convert 'my_app/my_module.ex' to 'MyApp.MyModule'." | |||
| (let* ((path (file-name-sans-extension path)) | |||
| (path (split-string path "/")) | |||
| (path (-remove (lambda (str) (equal str "")) path))) | |||
| (mapconcat #'alchemist-utils--snakecase-to-camelcase path "."))) | |||
| (defun alchemist-utils-add-trailing-slash (path) | |||
| "Add trailing slash to PATH if not already contain." | |||
| (if (not (string-match-p "/$" path)) | |||
| (format "%s/" path) | |||
| path)) | |||
| (defun alchemist-utils-occur-in-buffer-p (buffer regex) | |||
| "Return non-nil if BUFFER contains at least one occurrence of REGEX." | |||
| (with-current-buffer buffer | |||
| (save-excursion | |||
| (save-match-data | |||
| (goto-char (point-min)) | |||
| (re-search-forward regex nil t))))) | |||
| (defun alchemist-utils-jump-to-regex (regex before-fn after-fn search-fn reset-fn) | |||
| "Jump to REGEX using SEARCH-FN to search for it. | |||
| A common use case would be to use `re-search-forward' as the SEARCH-FN. | |||
| Call RESET-FN if the regex isn't found at the first try. BEFORE-FN is called | |||
| before performing the search while AFTER-FN after." | |||
| (when (alchemist-utils-occur-in-buffer-p (current-buffer) regex) | |||
| (save-match-data | |||
| (funcall before-fn) | |||
| (unless (funcall search-fn regex nil t) | |||
| (funcall reset-fn) | |||
| (funcall search-fn regex nil t)) | |||
| (funcall after-fn)))) | |||
| (defun alchemist-utils-jump-to-next-matching-line (regex after-fn) | |||
| "Jump to the next line matching REGEX. | |||
| Call AFTER-FN after performing the search." | |||
| (alchemist-utils-jump-to-regex regex 'end-of-line after-fn 're-search-forward 'beginning-of-buffer)) | |||
| (defun alchemist-utils-jump-to-previous-matching-line (regex after-fn) | |||
| "Jump to the previous line matching REGEX. | |||
| Call AFTER-FN after performing the search." | |||
| (alchemist-utils-jump-to-regex regex 'beginning-of-line after-fn 're-search-backward 'end-of-buffer)) | |||
| (defun alchemist-utils-elixir-version () | |||
| "Return the current Elixir version on the system." | |||
| (let* ((output (shell-command-to-string (format "%s --version" alchemist-execute-command))) | |||
| (output (split-string output "\n")) | |||
| (output (-remove (lambda (string) (alchemist-utils-empty-string-p string)) | |||
| output)) | |||
| (version (-last-item output)) | |||
| (version (replace-regexp-in-string "Elixir " "" version))) | |||
| version)) | |||
| (provide 'alchemist-utils) | |||
| ;;; alchemist-utils.el ends here | |||
| @ -0,0 +1,282 @@ | |||
| ;;; alchemist.el --- Elixir tooling integration into Emacs | |||
| ;; Copyright © 2014-2015 Samuel Tonini | |||
| ;; | |||
| ;; Author: Samuel Tonini <tonini.samuel@gmail.com> | |||
| ;; Maintainer: Samuel Tonini <tonini.samuel@gmail.com> | |||
| ;; URL: http://www.github.com/tonini/alchemist.el | |||
| ;; Version: 1.7.0 | |||
| ;; Package-Requires: ((elixir-mode "2.2.5") (dash "2.11.0") (emacs "24.4") (company "0.8.0") (pkg-info "0.4")) | |||
| ;; Keywords: languages, elixir, elixirc, mix, hex, alchemist | |||
| ;; 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: | |||
| ;; | |||
| ;; What Does Alchemist Do For You? | |||
| ;; | |||
| ;; Alchemist brings you all the Elixir tooling and power inside your Emacs editor. | |||
| ;; | |||
| ;; Alchemist comes with a bunch of features, which are: | |||
| ;; | |||
| ;; * Mix integration | |||
| ;; * Compile & Execution of Elixir code | |||
| ;; * Inline code evaluation | |||
| ;; * Inline macro expanding | |||
| ;; * Documentation lookup | |||
| ;; * Definition lookup | |||
| ;; * Powerful IEx integration | |||
| ;; * Smart code completion | |||
| ;; * Elixir project management | |||
| ;; * Phoenix support | |||
| ;;; Code: | |||
| ;; Tell the byte compiler about autoloaded functions from packages | |||
| (declare-function pkg-info-version-info "pkg-info" (package)) | |||
| (defgroup alchemist nil | |||
| "Elixir Tooling Integration Into Emacs." | |||
| :prefix "alchemist-" | |||
| :group 'applications | |||
| :link '(url-link :tag "Website" "http://www.alchemist-elixir.org") | |||
| :link '(url-link :tag "Github" "https://github.com/tonini/alchemist.el") | |||
| :link '(emacs-commentary-link :tag "Commentary" "alchemist")) | |||
| (defvar alchemist-mode-keymap nil) | |||
| (require 'easymenu) | |||
| (require 'company) | |||
| (require 'elixir-mode) | |||
| (require 'alchemist-utils) | |||
| (require 'alchemist-key) | |||
| (require 'alchemist-eval) | |||
| (require 'alchemist-goto) | |||
| (require 'alchemist-info) | |||
| (require 'alchemist-report) | |||
| (require 'alchemist-mix) | |||
| (require 'alchemist-hooks) | |||
| (require 'alchemist-message) | |||
| (require 'alchemist-iex) | |||
| (require 'alchemist-compile) | |||
| (require 'alchemist-refcard) | |||
| (require 'alchemist-complete) | |||
| (require 'alchemist-company) | |||
| (require 'alchemist-macroexpand) | |||
| (require 'alchemist-phoenix) | |||
| (defun alchemist-mode-hook () | |||
| "Hook which enables `alchemist-mode'" | |||
| (alchemist-mode 1)) | |||
| (defun alchemist-version (&optional show-version) | |||
| "Get the Alchemist version as string. | |||
| If called interactively or if SHOW-VERSION is non-nil, show the | |||
| version in the echo area and the messages buffer. | |||
| The returned string includes both, the version from package.el | |||
| and the library version, if both a present and different. | |||
| If the version number could not be determined, signal an error, | |||
| if called interactively, or if SHOW-VERSION is non-nil, otherwise | |||
| just return nil." | |||
| (interactive (list t)) | |||
| (let ((version (pkg-info-version-info 'alchemist))) | |||
| (when show-version | |||
| (message "Alchemist version: %s" version)) | |||
| version)) | |||
| (defun alchemist-elixir-version () | |||
| "Display the current Elixir version on the system." | |||
| (interactive) | |||
| (message "Elixir %s" (alchemist-utils-elixir-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-server-start-if-not-running) | |||
| (alchemist-test-initialize-modeline)) | |||
| (t | |||
| (alchemist-test-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 "r") 'alchemist-mix-rerun-last-test) | |||
| (define-key map (kbd "m c") 'alchemist-mix-compile) | |||
| (define-key map (kbd "m r") 'alchemist-mix-run) | |||
| (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 r") 'alchemist-refcard) | |||
| (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 "p t") 'alchemist-project-run-tests-for-current-file) | |||
| (define-key map (kbd "p l") 'alchemist-project-find-lib) | |||
| (define-key map (kbd "p f") 'alchemist-project-find-test) | |||
| (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 "i R") 'alchemist-iex-reload-module) | |||
| (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 map (kbd "v !") 'alchemist-eval-close-popup) | |||
| (define-key map (kbd "o l") 'alchemist-macroexpand-once-current-line) | |||
| (define-key map (kbd "o L") 'alchemist-macroexpand-once-print-current-line) | |||
| (define-key map (kbd "o k") 'alchemist-macroexpand-current-line) | |||
| (define-key map (kbd "o K") 'alchemist-macroexpand-print-current-line) | |||
| (define-key map (kbd "o i") 'alchemist-macroexpand-once-region) | |||
| (define-key map (kbd "o I") 'alchemist-macroexpand-once-print-region) | |||
| (define-key map (kbd "o r") 'alchemist-macroexpand-region) | |||
| (define-key map (kbd "o R") 'alchemist-macroexpand-print-region) | |||
| (define-key map (kbd "o !") 'alchemist-macroexpand-close-popup) | |||
| (define-key map (kbd "n i") 'alchemist-info-datatype-at-point) | |||
| (define-key map (kbd "n t") 'alchemist-info-types-at-point)) | |||
| (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) | |||
| (define-key alchemist-mode-map (kbd "M-P") 'alchemist-goto-jump-to-previous-def-symbol) | |||
| (define-key alchemist-mode-map (kbd "M-N") 'alchemist-goto-jump-to-next-def-symbol) | |||
| (define-key alchemist-mode-map (kbd "C-c M-r") 'alchemist-test-toggle-test-report-display) | |||
| (easy-menu-define alchemist-mode-menu alchemist-mode-map | |||
| "Alchemist mode menu." | |||
| '("Alchemist" | |||
| ("Goto" | |||
| ["Jump to definition 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]) | |||
| ("Macroexpand" | |||
| ["Macro expand once current line" alchemist-macroexpand-once-current-line] | |||
| ["Macro expand once current line and print" alchemist-macroexpand-print-current-line] | |||
| ["Macro expand current line" alchemist-macroexpand-current-line] | |||
| ["Macro expand current line and print" alchemist-macroexpand-print-current-line] | |||
| "---" | |||
| ["Macro expand once region" alchemist-macroexpand-once-region] | |||
| ["Macro expand once region and print" alchemist-macroexpand-print-region] | |||
| ["Macro expand region" alchemist-macroexpand-region] | |||
| ["Macro expand region and print" alchemist-macroexpand-print-region]) | |||
| ("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 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] | |||
| "---" | |||
| ["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 list all files inside test directory" alchemist-project-find-test] | |||
| ["Project list all files inside lib directory" alchemist-project-find-lib] | |||
| ["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]) | |||
| ("Documentation" | |||
| ["Documentation search..." alchemist-help] | |||
| ["Documentation search history..." alchemist-help-history] | |||
| "---" | |||
| ["Documentation search at point..." alchemist-help-search-at-point]) | |||
| ("About" | |||
| ["Show Alchemist version" alchemist-version t]))) | |||
| (add-hook 'elixir-mode-hook 'alchemist-mode-hook) | |||
| (provide 'alchemist) | |||
| ;;; alchemist.el ends here | |||
| @ -1,129 +0,0 @@ | |||
| ;;; async-autoloads.el --- automatically extracted autoloads | |||
| ;; | |||
| ;;; Code: | |||
| (add-to-list 'load-path (or (file-name-directory #$) (car load-path))) | |||
| ;;;### (autoloads nil "async" "async.el" (21898 47984 0 0)) | |||
| ;;; Generated autoloads from async.el | |||
| (autoload 'async-start-process "async" "\ | |||
| Start the executable PROGRAM asynchronously. See `async-start'. | |||
| PROGRAM is passed PROGRAM-ARGS, calling FINISH-FUNC with the | |||
| process object when done. If FINISH-FUNC is nil, the future | |||
| object will return the process object when the program is | |||
| finished. Set DEFAULT-DIRECTORY to change PROGRAM's current | |||
| working directory. | |||
| \(fn NAME PROGRAM FINISH-FUNC &rest PROGRAM-ARGS)" nil nil) | |||
| (autoload 'async-start "async" "\ | |||
| Execute START-FUNC (often a lambda) in a subordinate Emacs process. | |||
| When done, the return value is passed to FINISH-FUNC. Example: | |||
| (async-start | |||
| ;; What to do in the child process | |||
| (lambda () | |||
| (message \"This is a test\") | |||
| (sleep-for 3) | |||
| 222) | |||
| ;; What to do when it finishes | |||
| (lambda (result) | |||
| (message \"Async process done, result should be 222: %s\" | |||
| result))) | |||
| If FINISH-FUNC is nil or missing, a future is returned that can | |||
| be inspected using `async-get', blocking until the value is | |||
| ready. Example: | |||
| (let ((proc (async-start | |||
| ;; What to do in the child process | |||
| (lambda () | |||
| (message \"This is a test\") | |||
| (sleep-for 3) | |||
| 222)))) | |||
| (message \"I'm going to do some work here\") ;; .... | |||
| (message \"Waiting on async process, result should be 222: %s\" | |||
| (async-get proc))) | |||
| If you don't want to use a callback, and you don't care about any | |||
| return value form the child process, pass the `ignore' symbol as | |||
| the second argument (if you don't, and never call `async-get', it | |||
| will leave *emacs* process buffers hanging around): | |||
| (async-start | |||
| (lambda () | |||
| (delete-file \"a remote file on a slow link\" nil)) | |||
| 'ignore) | |||
| Note: Even when FINISH-FUNC is present, a future is still | |||
| returned except that it yields no value (since the value is | |||
| passed to FINISH-FUNC). Call `async-get' on such a future always | |||
| returns nil. It can still be useful, however, as an argument to | |||
| `async-ready' or `async-wait'. | |||
| \(fn START-FUNC &optional FINISH-FUNC)" nil t) | |||
| ;;;*** | |||
| ;;;### (autoloads nil "async-bytecomp" "async-bytecomp.el" (21898 | |||
| ;;;;;; 47984 0 0)) | |||
| ;;; Generated autoloads from async-bytecomp.el | |||
| (autoload 'async-byte-recompile-directory "async-bytecomp" "\ | |||
| Compile all *.el files in DIRECTORY asynchronously. | |||
| All *.elc files are systematically deleted before proceeding. | |||
| \(fn DIRECTORY &optional QUIET)" nil nil) | |||
| (defvar async-bytecomp-package-mode nil "\ | |||
| Non-nil if Async-Bytecomp-Package mode is enabled. | |||
| See the command `async-bytecomp-package-mode' for a description of this minor mode. | |||
| Setting this variable directly does not take effect; | |||
| either customize it (see the info node `Easy Customization') | |||
| or call the function `async-bytecomp-package-mode'.") | |||
| (custom-autoload 'async-bytecomp-package-mode "async-bytecomp" nil) | |||
| (autoload 'async-bytecomp-package-mode "async-bytecomp" "\ | |||
| Byte compile asynchronously packages installed with package.el. | |||
| Async compilation of packages can be controlled by | |||
| `async-bytecomp-allowed-packages'. | |||
| \(fn &optional ARG)" t nil) | |||
| ;;;*** | |||
| ;;;### (autoloads nil "dired-async" "dired-async.el" (21898 47984 | |||
| ;;;;;; 0 0)) | |||
| ;;; Generated autoloads from dired-async.el | |||
| (defvar dired-async-mode nil "\ | |||
| Non-nil if Dired-Async mode is enabled. | |||
| See the command `dired-async-mode' for a description of this minor mode. | |||
| Setting this variable directly does not take effect; | |||
| either customize it (see the info node `Easy Customization') | |||
| or call the function `dired-async-mode'.") | |||
| (custom-autoload 'dired-async-mode "dired-async" nil) | |||
| (autoload 'dired-async-mode "dired-async" "\ | |||
| Do dired actions asynchronously. | |||
| \(fn &optional ARG)" t nil) | |||
| ;;;*** | |||
| ;;;### (autoloads nil nil ("async-pkg.el" "smtpmail-async.el") (21898 | |||
| ;;;;;; 47984 290337 0)) | |||
| ;;;*** | |||
| ;; Local Variables: | |||
| ;; version-control: never | |||
| ;; no-byte-compile: t | |||
| ;; no-update-autoloads: t | |||
| ;; End: | |||
| ;;; async-autoloads.el ends here | |||
| @ -1,160 +0,0 @@ | |||
| ;;; async-bytecomp.el --- Async functions to compile elisp files async | |||
| ;; Copyright (C) 2014 John Wiegley | |||
| ;; Copyright (C) 2014 Thierry Volpiatto | |||
| ;; Authors: John Wiegley <jwiegley@gmail.com> | |||
| ;; Thierry Volpiatto <thierry.volpiatto@gmail.com> | |||
| ;; Keywords: dired async byte-compile | |||
| ;; X-URL: https://github.com/jwiegley/dired-async | |||
| ;; 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 2, 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 GNU Emacs; see the file COPYING. If not, write to the | |||
| ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, | |||
| ;; Boston, MA 02111-1307, USA. | |||
| ;;; Commentary: | |||
| ;; | |||
| ;; This package provide the `async-byte-recompile-directory' function | |||
| ;; which allows, as the name says to recompile a directory outside of | |||
| ;; your running emacs. | |||
| ;; The benefit is your files will be compiled in a clean environment without | |||
| ;; the old *.el files loaded. | |||
| ;; Among other things, this fix a bug in package.el which recompile | |||
| ;; the new files in the current environment with the old files loaded, creating | |||
| ;; errors in most packages after upgrades. | |||
| ;; | |||
| ;; NB: This package is advicing the function `package--compile'. | |||
| ;;; Code: | |||
| (require 'cl-lib) | |||
| (require 'async) | |||
| (defcustom async-bytecomp-allowed-packages '(async helm) | |||
| "Packages in this list will be compiled asynchronously by `package--compile'. | |||
| All the dependencies of these packages will be compiled async too, | |||
| so no need to add dependencies to this list. | |||
| The value of this variable can also be a list with a single element, | |||
| the symbol `all', in this case packages are always compiled asynchronously." | |||
| :group 'async | |||
| :type '(repeat (choice symbol))) | |||
| (defvar async-byte-compile-log-file "~/.emacs.d/async-bytecomp.log") | |||
| ;;;###autoload | |||
| (defun async-byte-recompile-directory (directory &optional quiet) | |||
| "Compile all *.el files in DIRECTORY asynchronously. | |||
| All *.elc files are systematically deleted before proceeding." | |||
| (cl-loop with dir = (directory-files directory t "\\.elc\\'") | |||
| unless dir return nil | |||
| for f in dir | |||
| when (file-exists-p f) do (delete-file f)) | |||
| ;; Ensure async is reloaded when async.elc is deleted. | |||
| ;; This happen when recompiling its own directory. | |||
| (load "async") | |||
| (let ((call-back | |||
| `(lambda (&optional ignore) | |||
| (if (file-exists-p async-byte-compile-log-file) | |||
| (let ((buf (get-buffer-create byte-compile-log-buffer)) | |||
| (n 0)) | |||
| (with-current-buffer buf | |||
| (goto-char (point-max)) | |||
| (let ((inhibit-read-only t)) | |||
| (insert-file-contents async-byte-compile-log-file) | |||
| (compilation-mode)) | |||
| (display-buffer buf) | |||
| (delete-file async-byte-compile-log-file) | |||
| (unless ,quiet | |||
| (save-excursion | |||
| (goto-char (point-min)) | |||
| (while (re-search-forward "^.*:Error:" nil t) | |||
| (cl-incf n))) | |||
| (if (> n 0) | |||
| (message "Failed to compile %d files in directory `%s'" n ,directory) | |||
| (message "Directory `%s' compiled asynchronously with warnings" ,directory))))) | |||
| (unless ,quiet | |||
| (message "Directory `%s' compiled asynchronously with success" ,directory)))))) | |||
| (async-start | |||
| `(lambda () | |||
| (require 'bytecomp) | |||
| ,(async-inject-variables "\\`\\(load-path\\)\\|byte\\'") | |||
| (let ((default-directory (file-name-as-directory ,directory)) | |||
| error-data) | |||
| (add-to-list 'load-path default-directory) | |||
| (byte-recompile-directory ,directory 0 t) | |||
| (when (get-buffer byte-compile-log-buffer) | |||
| (setq error-data (with-current-buffer byte-compile-log-buffer | |||
| (buffer-substring-no-properties (point-min) (point-max)))) | |||
| (unless (string= error-data "") | |||
| (with-temp-file ,async-byte-compile-log-file | |||
| (erase-buffer) | |||
| (insert error-data)))))) | |||
| call-back) | |||
| (unless quiet (message "Started compiling asynchronously directory %s" directory)))) | |||
| (defvar package-archive-contents) | |||
| (declare-function package-desc-reqs "package.el" (cl-x)) | |||
| (defun async-bytecomp--get-package-deps (pkg &optional only) | |||
| (let* ((pkg-desc (cadr (assq pkg package-archive-contents))) | |||
| (direct-deps (cl-loop for p in (package-desc-reqs pkg-desc) | |||
| for name = (car p) | |||
| when (assq name package-archive-contents) | |||
| collect name)) | |||
| (indirect-deps (unless (eq only 'direct) | |||
| (delete-dups | |||
| (cl-loop for p in direct-deps append | |||
| (async-bytecomp--get-package-deps p)))))) | |||
| (cl-case only | |||
| (direct direct-deps) | |||
| (separate (list direct-deps indirect-deps)) | |||
| (indirect indirect-deps) | |||
| (t (delete-dups (append direct-deps indirect-deps)))))) | |||
| (defun async-bytecomp-get-allowed-pkgs () | |||
| (when (and async-bytecomp-allowed-packages | |||
| (listp async-bytecomp-allowed-packages)) | |||
| (cl-loop for p in async-bytecomp-allowed-packages | |||
| append (async-bytecomp--get-package-deps p) into reqs | |||
| finally return | |||
| (delete-dups | |||
| (append async-bytecomp-allowed-packages reqs))))) | |||
| (defadvice package--compile (around byte-compile-async) | |||
| (let ((cur-package (package-desc-name pkg-desc))) | |||
| (if (or (equal async-bytecomp-allowed-packages '(all)) | |||
| (memq cur-package (async-bytecomp-get-allowed-pkgs))) | |||
| (progn | |||
| (when (eq cur-package 'async) | |||
| (fmakunbound 'async-byte-recompile-directory)) | |||
| (package-activate-1 pkg-desc) | |||
| (load "async-bytecomp") ; emacs-24.3 don't reload new files. | |||
| (async-byte-recompile-directory (package-desc-dir pkg-desc) t)) | |||
| ad-do-it))) | |||
| ;;;###autoload | |||
| (define-minor-mode async-bytecomp-package-mode | |||
| "Byte compile asynchronously packages installed with package.el. | |||
| Async compilation of packages can be controlled by | |||
| `async-bytecomp-allowed-packages'." | |||
| :group 'async | |||
| :global t | |||
| (if async-bytecomp-package-mode | |||
| (ad-activate 'package--compile) | |||
| (ad-deactivate 'package--compile))) | |||
| (provide 'async-bytecomp) | |||
| ;;; async-bytecomp.el ends here | |||
| @ -1,4 +0,0 @@ | |||
| (define-package "async" "20150529.529" "Asynchronous processing in Emacs" 'nil) | |||
| ;; Local Variables: | |||
| ;; no-byte-compile: t | |||
| ;; End: | |||
| @ -1,292 +0,0 @@ | |||
| ;;; async --- Asynchronous processing in Emacs | |||
| ;; Copyright (C) 2012~2014 John Wiegley | |||
| ;; Author: John Wiegley <jwiegley@gmail.com> | |||
| ;; Created: 18 Jun 2012 | |||
| ;; Keywords: async | |||
| ;; X-URL: https://github.com/jwiegley/emacs-async | |||
| ;; 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 2, 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 GNU Emacs; see the file COPYING. If not, write to the | |||
| ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, | |||
| ;; Boston, MA 02111-1307, USA. | |||
| ;;; Commentary: | |||
| ;; Adds the ability to call asynchronous functions and process with ease. See | |||
| ;; the documentation for `async-start' and `async-start-process'. | |||
| ;;; Code: | |||
| (defgroup async nil | |||
| "Simple asynchronous processing in Emacs" | |||
| :group 'emacs) | |||
| (defvar async-debug nil) | |||
| (defvar async-send-over-pipe t) | |||
| (defvar async-in-child-emacs nil) | |||
| (defvar async-callback nil) | |||
| (defvar async-callback-for-process nil) | |||
| (defvar async-callback-value nil) | |||
| (defvar async-callback-value-set nil) | |||
| (defvar async-current-process nil) | |||
| (defun async-inject-variables | |||
| (include-regexp &optional predicate exclude-regexp) | |||
| "Return a `setq' form that replicates part of the calling environment. | |||
| It sets the value for every variable matching INCLUDE-REGEXP and | |||
| also PREDICATE. It will not perform injection for any variable | |||
| matching EXCLUDE-REGEXP (if present). It is intended to be used | |||
| as follows: | |||
| (async-start | |||
| `(lambda () | |||
| (require 'smtpmail) | |||
| (with-temp-buffer | |||
| (insert ,(buffer-substring-no-properties (point-min) (point-max))) | |||
| ;; Pass in the variable environment for smtpmail | |||
| ,(async-inject-variables \"\\`\\(smtpmail\\|\\(user-\\)?mail\\)-\") | |||
| (smtpmail-send-it))) | |||
| 'ignore)" | |||
| `(setq | |||
| ,@(let (bindings) | |||
| (mapatoms | |||
| (lambda (sym) | |||
| (if (and (boundp sym) | |||
| (or (null include-regexp) | |||
| (string-match include-regexp (symbol-name sym))) | |||
| (not (string-match | |||
| (or exclude-regexp "-syntax-table\\'") | |||
| (symbol-name sym)))) | |||
| (let ((value (symbol-value sym))) | |||
| (when (or (null predicate) | |||
| (funcall predicate sym)) | |||
| (setq bindings (cons `(quote ,value) bindings) | |||
| bindings (cons sym bindings))))))) | |||
| bindings))) | |||
| (defalias 'async-inject-environment 'async-inject-variables) | |||
| (defun async-handle-result (func result buf) | |||
| (if (null func) | |||
| (progn | |||
| (set (make-local-variable 'async-callback-value) result) | |||
| (set (make-local-variable 'async-callback-value-set) t)) | |||
| (unwind-protect | |||
| (if (and (listp result) | |||
| (eq 'async-signal (nth 0 result))) | |||
| (signal (car (nth 1 result)) | |||
| (cdr (nth 1 result))) | |||
| (funcall func result)) | |||
| (unless async-debug | |||
| (kill-buffer buf))))) | |||
| (defun async-when-done (proc &optional change) | |||
| "Process sentinal used to retrieve the value from the child process." | |||
| (when (eq 'exit (process-status proc)) | |||
| (with-current-buffer (process-buffer proc) | |||
| (let ((async-current-process proc)) | |||
| (if (= 0 (process-exit-status proc)) | |||
| (if async-callback-for-process | |||
| (if async-callback | |||
| (prog1 | |||
| (funcall async-callback proc) | |||
| (unless async-debug | |||
| (kill-buffer (current-buffer)))) | |||
| (set (make-local-variable 'async-callback-value) proc) | |||
| (set (make-local-variable 'async-callback-value-set) t)) | |||
| (goto-char (point-max)) | |||
| (backward-sexp) | |||
| (async-handle-result async-callback (read (current-buffer)) | |||
| (current-buffer))) | |||
| (set (make-local-variable 'async-callback-value) | |||
| (list 'error | |||
| (format "Async process '%s' failed with exit code %d" | |||
| (process-name proc) (process-exit-status proc)))) | |||
| (set (make-local-variable 'async-callback-value-set) t)))))) | |||
| (defun async--receive-sexp (&optional stream) | |||
| (let ((sexp (decode-coding-string (base64-decode-string | |||
| (read stream)) 'utf-8-unix))) | |||
| (if async-debug | |||
| (message "Received sexp {{{%s}}}" (pp-to-string sexp))) | |||
| (setq sexp (read sexp)) | |||
| (if async-debug | |||
| (message "Read sexp {{{%s}}}" (pp-to-string sexp))) | |||
| (eval sexp))) | |||
| (defun async--insert-sexp (sexp) | |||
| (prin1 sexp (current-buffer)) | |||
| ;; Just in case the string we're sending might contain EOF | |||
| (encode-coding-region (point-min) (point-max) 'utf-8-unix) | |||
| (base64-encode-region (point-min) (point-max) t) | |||
| (goto-char (point-min)) (insert ?\") | |||
| (goto-char (point-max)) (insert ?\" ?\n)) | |||
| (defun async--transmit-sexp (process sexp) | |||
| (with-temp-buffer | |||
| (if async-debug | |||
| (message "Transmitting sexp {{{%s}}}" (pp-to-string sexp))) | |||
| (async--insert-sexp sexp) | |||
| (process-send-region process (point-min) (point-max)))) | |||
| (defun async-batch-invoke () | |||
| "Called from the child Emacs process' command-line." | |||
| (setq async-in-child-emacs t | |||
| debug-on-error async-debug) | |||
| (if debug-on-error | |||
| (prin1 (funcall | |||
| (async--receive-sexp (unless async-send-over-pipe | |||
| command-line-args-left)))) | |||
| (condition-case err | |||
| (prin1 (funcall | |||
| (async--receive-sexp (unless async-send-over-pipe | |||
| command-line-args-left)))) | |||
| (error | |||
| (prin1 (list 'async-signal err)))))) | |||
| (defun async-ready (future) | |||
| "Query a FUTURE to see if the ready is ready -- i.e., if no blocking | |||
| would result from a call to `async-get' on that FUTURE." | |||
| (and (memq (process-status future) '(exit signal)) | |||
| (with-current-buffer (process-buffer future) | |||
| async-callback-value-set))) | |||
| (defun async-wait (future) | |||
| "Wait for FUTURE to become ready." | |||
| (while (not (async-ready future)) | |||
| (sit-for 0.05))) | |||
| (defun async-get (future) | |||
| "Get the value from an asynchronously function when it is ready. | |||
| FUTURE is returned by `async-start' or `async-start-process' when | |||
| its FINISH-FUNC is nil." | |||
| (async-wait future) | |||
| (with-current-buffer (process-buffer future) | |||
| (async-handle-result #'identity async-callback-value (current-buffer)))) | |||
| (defun async-message-p (value) | |||
| "Return true of VALUE is an async.el message packet." | |||
| (and (listp value) | |||
| (plist-get value :async-message))) | |||
| (defun async-send (&rest args) | |||
| "Send the given messages to the asychronous Emacs PROCESS." | |||
| (let ((args (append args '(:async-message t)))) | |||
| (if async-in-child-emacs | |||
| (if async-callback | |||
| (funcall async-callback args)) | |||
| (async--transmit-sexp (car args) (list 'quote (cdr args)))))) | |||
| (defun async-receive (&rest args) | |||
| "Send the given messages to the asychronous Emacs PROCESS." | |||
| (async--receive-sexp)) | |||
| ;;;###autoload | |||
| (defun async-start-process (name program finish-func &rest program-args) | |||
| "Start the executable PROGRAM asynchronously. See `async-start'. | |||
| PROGRAM is passed PROGRAM-ARGS, calling FINISH-FUNC with the | |||
| process object when done. If FINISH-FUNC is nil, the future | |||
| object will return the process object when the program is | |||
| finished. Set DEFAULT-DIRECTORY to change PROGRAM's current | |||
| working directory." | |||
| (let* ((buf (generate-new-buffer (concat "*" name "*"))) | |||
| (proc (let ((process-connection-type nil)) | |||
| (apply #'start-process name buf program program-args)))) | |||
| (with-current-buffer buf | |||
| (set (make-local-variable 'async-callback) finish-func) | |||
| (set-process-sentinel proc #'async-when-done) | |||
| (unless (string= name "emacs") | |||
| (set (make-local-variable 'async-callback-for-process) t)) | |||
| proc))) | |||
| ;;;###autoload | |||
| (defmacro async-start (start-func &optional finish-func) | |||
| "Execute START-FUNC (often a lambda) in a subordinate Emacs process. | |||
| When done, the return value is passed to FINISH-FUNC. Example: | |||
| (async-start | |||
| ;; What to do in the child process | |||
| (lambda () | |||
| (message \"This is a test\") | |||
| (sleep-for 3) | |||
| 222) | |||
| ;; What to do when it finishes | |||
| (lambda (result) | |||
| (message \"Async process done, result should be 222: %s\" | |||
| result))) | |||
| If FINISH-FUNC is nil or missing, a future is returned that can | |||
| be inspected using `async-get', blocking until the value is | |||
| ready. Example: | |||
| (let ((proc (async-start | |||
| ;; What to do in the child process | |||
| (lambda () | |||
| (message \"This is a test\") | |||
| (sleep-for 3) | |||
| 222)))) | |||
| (message \"I'm going to do some work here\") ;; .... | |||
| (message \"Waiting on async process, result should be 222: %s\" | |||
| (async-get proc))) | |||
| If you don't want to use a callback, and you don't care about any | |||
| return value form the child process, pass the `ignore' symbol as | |||
| the second argument (if you don't, and never call `async-get', it | |||
| will leave *emacs* process buffers hanging around): | |||
| (async-start | |||
| (lambda () | |||
| (delete-file \"a remote file on a slow link\" nil)) | |||
| 'ignore) | |||
| Note: Even when FINISH-FUNC is present, a future is still | |||
| returned except that it yields no value (since the value is | |||
| passed to FINISH-FUNC). Call `async-get' on such a future always | |||
| returns nil. It can still be useful, however, as an argument to | |||
| `async-ready' or `async-wait'." | |||
| (require 'find-func) | |||
| (let ((procvar (make-symbol "proc"))) | |||
| `(let* ((sexp ,start-func) | |||
| (,procvar | |||
| (async-start-process | |||
| "emacs" (file-truename | |||
| (expand-file-name invocation-name | |||
| invocation-directory)) | |||
| ,finish-func | |||
| "-Q" "-l" | |||
| ;; Using `locate-library' ensure we use the right file | |||
| ;; when the .elc have been deleted. | |||
| ,(locate-library "async") | |||
| "-batch" "-f" "async-batch-invoke" | |||
| (if async-send-over-pipe | |||
| "<none>" | |||
| (with-temp-buffer | |||
| (async--insert-sexp (list 'quote sexp)) | |||
| (buffer-string)))))) | |||
| (if async-send-over-pipe | |||
| (async--transmit-sexp ,procvar (list 'quote sexp))) | |||
| ,procvar))) | |||
| (defmacro async-sandbox(func) | |||
| "Evaluate FUNC in a separate Emacs process, synchronously." | |||
| `(async-get (async-start ,func))) | |||
| (provide 'async) | |||
| ;;; async.el ends here | |||
| @ -1,282 +0,0 @@ | |||
| ;;; dired-async.el --- Copy/move/delete asynchronously in dired. | |||
| ;; Copyright (C) 2012~2014 John Wiegley | |||
| ;; Copyright (C) 2012~2014 Thierry Volpiatto | |||
| ;; Authors: John Wiegley <jwiegley@gmail.com> | |||
| ;; Thierry Volpiatto <thierry.volpiatto@gmail.com> | |||
| ;; Keywords: dired async network | |||
| ;; X-URL: https://github.com/jwiegley/dired-async | |||
| ;; 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 2, 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 GNU Emacs; see the file COPYING. If not, write to the | |||
| ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, | |||
| ;; Boston, MA 02111-1307, USA. | |||
| ;;; Commentary: | |||
| ;; This file provide a redefinition of `dired-create-file' function, | |||
| ;; which must be loaded *after* dired-aux.el, performs copies, | |||
| ;; moves and all what is handled by `dired-create-file' in the background | |||
| ;; using a slave Emacs process, by means of the async.el module. | |||
| ;; To use it, put this in your .emacs: | |||
| ;; | |||
| ;; (eval-after-load "dired-aux" | |||
| ;; '(require 'dired-async)) | |||
| ;; | |||
| ;; | |||
| ;;; Code: | |||
| (require 'cl-lib) | |||
| (require 'dired-aux) | |||
| (require 'async) | |||
| (eval-when-compile | |||
| (defvar async-callback)) | |||
| (defvar dired-async-operation nil) | |||
| (defgroup dired-async nil | |||
| "Copy rename files asynchronously from dired." | |||
| :group 'dired) | |||
| (defcustom dired-async-env-variables-regexp | |||
| "\\`\\(tramp-\\(default\\|connection\\|remote\\)\\|ange-ftp\\)-.*" | |||
| "Variables matching this regexp will be loaded on Child Emacs." | |||
| :type 'regexp | |||
| :group 'dired-async) | |||
| (defcustom dired-async-message-function 'dired-async-mode-line-message | |||
| "Function to use to notify result when operation finish. | |||
| Should take same args as `message'." | |||
| :group 'dired-async | |||
| :type 'function) | |||
| (defcustom dired-async-log-file "/tmp/dired-async.log" | |||
| "File use to communicate errors from Child Emacs to host Emacs." | |||
| :group 'dired-async | |||
| :type 'string) | |||
| (defface dired-async-message | |||
| '((t (:foreground "yellow"))) | |||
| "Face used for mode-line message." | |||
| :group 'dired-async) | |||
| (defface dired-async-mode-message | |||
| '((t (:background "Firebrick1"))) | |||
| "Face used for `dired-async--modeline-mode' lighter." | |||
| :group 'dired-async) | |||
| (define-minor-mode dired-async--modeline-mode | |||
| "Notify mode-line that an async process run." | |||
| :group 'dired-async | |||
| :global t | |||
| :lighter (:eval (propertize (format " [%s Async job(s) running]" | |||
| (length (dired-async-processes))) | |||
| 'face 'dired-async-mode-message)) | |||
| (unless dired-async--modeline-mode | |||
| (let ((visible-bell t)) (ding)))) | |||
| (defun dired-async-mode-line-message (text &rest args) | |||
| "Notify end of operation in `mode-line'." | |||
| (message nil) | |||
| (let ((mode-line-format (concat | |||
| " " (propertize | |||
| (if args | |||
| (apply #'format text args) | |||
| text) | |||
| 'face 'dired-async-message)))) | |||
| (force-mode-line-update) | |||
| (sit-for 3) | |||
| (force-mode-line-update))) | |||
| (defun dired-async-processes () | |||
| (cl-loop for p in (process-list) | |||
| when (cl-loop for c in (process-command p) thereis | |||
| (string= "async-batch-invoke" c)) | |||
| collect p)) | |||
| (defun dired-async-kill-process () | |||
| (interactive) | |||
| (let* ((processes (dired-async-processes)) | |||
| (proc (car (last processes)))) | |||
| (delete-process proc) | |||
| (unless (> (length processes) 1) | |||
| (dired-async--modeline-mode -1)))) | |||
| (defun dired-async-after-file-create (len-flist) | |||
| "Callback function used for operation handled by `dired-create-file'." | |||
| (unless (dired-async-processes) | |||
| ;; Turn off mode-line notification | |||
| ;; only when last process end. | |||
| (dired-async--modeline-mode -1)) | |||
| (when dired-async-operation | |||
| (if (file-exists-p dired-async-log-file) | |||
| (progn | |||
| (pop-to-buffer (get-buffer-create "*dired async*")) | |||
| (erase-buffer) | |||
| (insert "Error: ") | |||
| (insert-file-contents dired-async-log-file) | |||
| (delete-file dired-async-log-file)) | |||
| (run-with-timer | |||
| 0.1 nil | |||
| dired-async-message-function "Asynchronous %s of %s file(s) on %s file(s) done" | |||
| (car dired-async-operation) (cadr dired-async-operation) len-flist)))) | |||
| (defun dired-async-maybe-kill-ftp () | |||
| "Return a form to kill ftp process in child emacs." | |||
| (quote | |||
| (progn | |||
| (require 'cl-lib) | |||
| (let ((buf (cl-loop for b in (buffer-list) | |||
| thereis (and (string-match | |||
| "\\`\\*ftp.*" | |||
| (buffer-name b)) b)))) | |||
| (when buf (kill-buffer buf)))))) | |||
| (defun dired-async-create-files (file-creator operation fn-list name-constructor | |||
| &optional marker-char) | |||
| "Same as `dired-create-files' but asynchronous. | |||
| See `dired-create-files' for the behavior of arguments." | |||
| (setq dired-async-operation nil) | |||
| (let (dired-create-files-failures failures async-fn-list | |||
| skipped (success-count 0) (total (length fn-list)) | |||
| (callback `(lambda (&optional ignore) | |||
| (dired-async-after-file-create ,(length fn-list))))) | |||
| (let (to overwrite-query | |||
| overwrite-backup-query) ; for dired-handle-overwrite | |||
| (dolist (from fn-list) | |||
| (setq to (funcall name-constructor from)) | |||
| (if (equal to from) | |||
| (progn | |||
| (setq to nil) | |||
| (dired-log "Cannot %s to same file: %s\n" | |||
| (downcase operation) from))) | |||
| (if (not to) | |||
| (setq skipped (cons (dired-make-relative from) skipped)) | |||
| (let* ((overwrite (file-exists-p to)) | |||
| (dired-overwrite-confirmed ; for dired-handle-overwrite | |||
| (and overwrite | |||
| (let ((help-form '(format "\ | |||
| Type SPC or `y' to overwrite file `%s', | |||
| DEL or `n' to skip to next, | |||
| ESC or `q' to not overwrite any of the remaining files, | |||
| `!' to overwrite all remaining files with no more questions." to))) | |||
| (dired-query 'overwrite-query | |||
| "Overwrite `%s'?" to)))) | |||
| ;; must determine if FROM is marked before file-creator | |||
| ;; gets a chance to delete it (in case of a move). | |||
| (actual-marker-char | |||
| (cond ((integerp marker-char) marker-char) | |||
| (marker-char (dired-file-marker from)) ; slow | |||
| (t nil)))) | |||
| ;; Handle the `dired-copy-file' file-creator specially | |||
| ;; When copying a directory to another directory or | |||
| ;; possibly to itself or one of its subdirectories. | |||
| ;; e.g "~/foo/" => "~/test/" | |||
| ;; or "~/foo/" =>"~/foo/" | |||
| ;; or "~/foo/ => ~/foo/bar/") | |||
| ;; In this case the 'name-constructor' have set the destination | |||
| ;; TO to "~/test/foo" because the old emacs23 behavior | |||
| ;; of `copy-directory' was to not create the subdirectory | |||
| ;; and instead copy the contents. | |||
| ;; With the new behavior of `copy-directory' | |||
| ;; (similar to the `cp' shell command) we don't | |||
| ;; need such a construction of the target directory, | |||
| ;; so modify the destination TO to "~/test/" instead of "~/test/foo/". | |||
| (let ((destname (file-name-directory to))) | |||
| (when (and (file-directory-p from) | |||
| (file-directory-p to) | |||
| (eq file-creator 'dired-copy-file)) | |||
| (setq to destname)) | |||
| ;; If DESTNAME is a subdirectory of FROM, not a symlink, | |||
| ;; and the method in use is copying, signal an error. | |||
| (and (eq t (car (file-attributes destname))) | |||
| (eq file-creator 'dired-copy-file) | |||
| (file-in-directory-p destname from) | |||
| (error "Cannot copy `%s' into its subdirectory `%s'" | |||
| from to))) | |||
| (if overwrite | |||
| (or (and dired-overwrite-confirmed | |||
| (push (cons from to) async-fn-list)) | |||
| (progn | |||
| (push (dired-make-relative from) failures) | |||
| (dired-log "%s `%s' to `%s' failed" | |||
| operation from to))) | |||
| (push (cons from to) async-fn-list)))))) | |||
| ;; Handle error happening in host emacs. | |||
| (cond | |||
| (dired-create-files-failures | |||
| (setq failures (nconc failures dired-create-files-failures)) | |||
| (dired-log-summary | |||
| (format "%s failed for %d file%s in %d requests" | |||
| operation (length failures) | |||
| (dired-plural-s (length failures)) | |||
| total) | |||
| failures)) | |||
| (failures | |||
| (dired-log-summary | |||
| (format "%s failed for %d of %d file%s" | |||
| operation (length failures) | |||
| total (dired-plural-s total)) | |||
| failures)) | |||
| (skipped | |||
| (dired-log-summary | |||
| (format "%s: %d of %d file%s skipped" | |||
| operation (length skipped) total | |||
| (dired-plural-s total)) | |||
| skipped)) | |||
| (t (message "%s: %s file%s" | |||
| operation success-count (dired-plural-s success-count)))) | |||
| ;; Start async process. | |||
| (when async-fn-list | |||
| (async-start `(lambda () | |||
| (require 'cl-lib) (require 'dired-aux) (require 'dired-x) | |||
| ,(async-inject-variables dired-async-env-variables-regexp) | |||
| (condition-case err | |||
| (let ((dired-recursive-copies (quote always))) | |||
| (cl-loop for (f . d) in (quote ,async-fn-list) | |||
| do (funcall (quote ,file-creator) f d t))) | |||
| (file-error | |||
| (with-temp-file ,dired-async-log-file | |||
| (insert (format "%S" err))))) | |||
| ,(dired-async-maybe-kill-ftp)) | |||
| callback) | |||
| ;; Run mode-line notifications while process running. | |||
| (dired-async--modeline-mode 1) | |||
| (setq dired-async-operation (list operation (length async-fn-list))) | |||
| (message "%s proceeding asynchronously..." operation)))) | |||
| (defadvice dired-create-files (around dired-async) | |||
| (dired-async-create-files file-creator operation fn-list | |||
| name-constructor marker-char)) | |||
| ;;;###autoload | |||
| (define-minor-mode dired-async-mode | |||
| "Do dired actions asynchronously." | |||
| :group 'dired-async | |||
| :global t | |||
| (if dired-async-mode | |||
| (if (fboundp 'advice-add) | |||
| (advice-add 'dired-create-files :override #'dired-async-create-files) | |||
| (ad-activate 'dired-create-files)) | |||
| (if (fboundp 'advice-remove) | |||
| (advice-remove 'dired-create-files #'dired-async-create-files) | |||
| (ad-deactivate 'dired-create-files)))) | |||
| (provide 'dired-async) | |||
| ;;; dired-async.el ends here | |||
| @ -1,73 +0,0 @@ | |||
| ;;; smtpmail-async --- Send e-mail with smtpmail.el asynchronously | |||
| ;; Copyright (C) 2012~2014 John Wiegley | |||
| ;; Author: John Wiegley <jwiegley@gmail.com> | |||
| ;; Created: 18 Jun 2012 | |||
| ;; Keywords: email async | |||
| ;; X-URL: https://github.com/jwiegley/emacs-async | |||
| ;; 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 2, 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 GNU Emacs; see the file COPYING. If not, write to the | |||
| ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, | |||
| ;; Boston, MA 02111-1307, USA. | |||
| ;;; Commentary: | |||
| ;; Send e-mail with smtpmail.el asynchronously. To use: | |||
| ;; | |||
| ;; (require 'smtpmail-async) | |||
| ;; | |||
| ;; (setq send-mail-function 'async-smtpmail-send-it | |||
| ;; message-send-mail-function 'async-smtpmail-send-it) | |||
| ;; | |||
| ;; This assumes you already have smtpmail.el working. | |||
| ;;; Code: | |||
| (defgroup smtpmail-async nil | |||
| "Send e-mail with smtpmail.el asynchronously" | |||
| :group 'smptmail) | |||
| (require 'async) | |||
| (require 'smtpmail) | |||
| (require 'message) | |||
| (defvar async-smtpmail-before-send-hook nil | |||
| "Hook running in the child emacs in `async-smtpmail-send-it'. | |||
| It is called just before calling `smtpmail-send-it'.") | |||
| (defun async-smtpmail-send-it () | |||
| (let ((to (message-field-value "To")) | |||
| (buf-content (buffer-substring-no-properties | |||
| (point-min) (point-max)))) | |||
| (message "Delivering message to %s..." to) | |||
| (async-start | |||
| `(lambda () | |||
| (require 'smtpmail) | |||
| (with-temp-buffer | |||
| (insert ,buf-content) | |||
| (set-buffer-multibyte nil) | |||
| ;; Pass in the variable environment for smtpmail | |||
| ,(async-inject-variables | |||
| "\\`\\(smtpmail\\|async-smtpmail\\|\\(user-\\)?mail\\)-\\|auth-sources" | |||
| nil "\\`\\(mail-header-format-function\\|smtpmail-address-buffer\\|mail-mode-abbrev-table\\)") | |||
| (run-hooks 'async-smtpmail-before-send-hook) | |||
| (smtpmail-send-it))) | |||
| `(lambda (&optional ignore) | |||
| (message "Delivering message to %s...done" ,to))))) | |||
| (provide 'smtpmail-async) | |||
| ;;; smtpmail-async.el ends here | |||
| @ -0,0 +1,129 @@ | |||
| ;;; async-autoloads.el --- automatically extracted autoloads | |||
| ;; | |||
| ;;; Code: | |||
| (add-to-list 'load-path (or (file-name-directory #$) (car load-path))) | |||
| ;;;### (autoloads nil "async" "async.el" (22171 46585 0 0)) | |||
| ;;; Generated autoloads from async.el | |||
| (autoload 'async-start-process "async" "\ | |||
| Start the executable PROGRAM asynchronously. See `async-start'. | |||
| PROGRAM is passed PROGRAM-ARGS, calling FINISH-FUNC with the | |||
| process object when done. If FINISH-FUNC is nil, the future | |||
| object will return the process object when the program is | |||
| finished. Set DEFAULT-DIRECTORY to change PROGRAM's current | |||
| working directory. | |||
| \(fn NAME PROGRAM FINISH-FUNC &rest PROGRAM-ARGS)" nil nil) | |||
| (autoload 'async-start "async" "\ | |||
| Execute START-FUNC (often a lambda) in a subordinate Emacs process. | |||
| When done, the return value is passed to FINISH-FUNC. Example: | |||
| (async-start | |||
| ;; What to do in the child process | |||
| (lambda () | |||
| (message \"This is a test\") | |||
| (sleep-for 3) | |||
| 222) | |||
| ;; What to do when it finishes | |||
| (lambda (result) | |||
| (message \"Async process done, result should be 222: %s\" | |||
| result))) | |||
| If FINISH-FUNC is nil or missing, a future is returned that can | |||
| be inspected using `async-get', blocking until the value is | |||
| ready. Example: | |||
| (let ((proc (async-start | |||
| ;; What to do in the child process | |||
| (lambda () | |||
| (message \"This is a test\") | |||
| (sleep-for 3) | |||
| 222)))) | |||
| (message \"I'm going to do some work here\") ;; .... | |||
| (message \"Waiting on async process, result should be 222: %s\" | |||
| (async-get proc))) | |||
| If you don't want to use a callback, and you don't care about any | |||
| return value form the child process, pass the `ignore' symbol as | |||
| the second argument (if you don't, and never call `async-get', it | |||
| will leave *emacs* process buffers hanging around): | |||
| (async-start | |||
| (lambda () | |||
| (delete-file \"a remote file on a slow link\" nil)) | |||
| 'ignore) | |||
| Note: Even when FINISH-FUNC is present, a future is still | |||
| returned except that it yields no value (since the value is | |||
| passed to FINISH-FUNC). Call `async-get' on such a future always | |||
| returns nil. It can still be useful, however, as an argument to | |||
| `async-ready' or `async-wait'. | |||
| \(fn START-FUNC &optional FINISH-FUNC)" nil nil) | |||
| ;;;*** | |||
| ;;;### (autoloads nil "async-bytecomp" "async-bytecomp.el" (22171 | |||
| ;;;;;; 46585 0 0)) | |||
| ;;; Generated autoloads from async-bytecomp.el | |||
| (autoload 'async-byte-recompile-directory "async-bytecomp" "\ | |||
| Compile all *.el files in DIRECTORY asynchronously. | |||
| All *.elc files are systematically deleted before proceeding. | |||
| \(fn DIRECTORY &optional QUIET)" nil nil) | |||
| (defvar async-bytecomp-package-mode nil "\ | |||
| Non-nil if Async-Bytecomp-Package mode is enabled. | |||
| See the command `async-bytecomp-package-mode' for a description of this minor mode. | |||
| Setting this variable directly does not take effect; | |||
| either customize it (see the info node `Easy Customization') | |||
| or call the function `async-bytecomp-package-mode'.") | |||
| (custom-autoload 'async-bytecomp-package-mode "async-bytecomp" nil) | |||
| (autoload 'async-bytecomp-package-mode "async-bytecomp" "\ | |||
| Byte compile asynchronously packages installed with package.el. | |||
| Async compilation of packages can be controlled by | |||
| `async-bytecomp-allowed-packages'. | |||
| \(fn &optional ARG)" t nil) | |||
| ;;;*** | |||
| ;;;### (autoloads nil "dired-async" "dired-async.el" (22171 46585 | |||
| ;;;;;; 0 0)) | |||
| ;;; Generated autoloads from dired-async.el | |||
| (defvar dired-async-mode nil "\ | |||
| Non-nil if Dired-Async mode is enabled. | |||
| See the command `dired-async-mode' for a description of this minor mode. | |||
| Setting this variable directly does not take effect; | |||
| either customize it (see the info node `Easy Customization') | |||
| or call the function `dired-async-mode'.") | |||
| (custom-autoload 'dired-async-mode "dired-async" nil) | |||
| (autoload 'dired-async-mode "dired-async" "\ | |||
| Do dired actions asynchronously. | |||
| \(fn &optional ARG)" t nil) | |||
| ;;;*** | |||
| ;;;### (autoloads nil nil ("async-pkg.el" "smtpmail-async.el") (22171 | |||
| ;;;;;; 46585 642069 0)) | |||
| ;;;*** | |||
| ;; Local Variables: | |||
| ;; version-control: never | |||
| ;; no-byte-compile: t | |||
| ;; no-update-autoloads: t | |||
| ;; End: | |||
| ;;; async-autoloads.el ends here | |||
| @ -0,0 +1,177 @@ | |||
| ;;; async-bytecomp.el --- Async functions to compile elisp files async | |||
| ;; Copyright (C) 2014-2016 Free Software Foundation, Inc. | |||
| ;; Authors: John Wiegley <jwiegley@gmail.com> | |||
| ;; Thierry Volpiatto <thierry.volpiatto@gmail.com> | |||
| ;; Keywords: dired async byte-compile | |||
| ;; X-URL: https://github.com/jwiegley/dired-async | |||
| ;; 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 2, 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 GNU Emacs; see the file COPYING. If not, write to the | |||
| ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, | |||
| ;; Boston, MA 02111-1307, USA. | |||
| ;;; Commentary: | |||
| ;; | |||
| ;; This package provide the `async-byte-recompile-directory' function | |||
| ;; which allows, as the name says to recompile a directory outside of | |||
| ;; your running emacs. | |||
| ;; The benefit is your files will be compiled in a clean environment without | |||
| ;; the old *.el files loaded. | |||
| ;; Among other things, this fix a bug in package.el which recompile | |||
| ;; the new files in the current environment with the old files loaded, creating | |||
| ;; errors in most packages after upgrades. | |||
| ;; | |||
| ;; NB: This package is advicing the function `package--compile'. | |||
| ;;; Code: | |||
| (require 'cl-lib) | |||
| (require 'async) | |||
| (defcustom async-bytecomp-allowed-packages | |||
| '(async helm helm-core helm-ls-git helm-ls-hg magit) | |||
| "Packages in this list will be compiled asynchronously by `package--compile'. | |||
| All the dependencies of these packages will be compiled async too, | |||
| so no need to add dependencies to this list. | |||
| The value of this variable can also be a list with a single element, | |||
| the symbol `all', in this case packages are always compiled asynchronously." | |||
| :group 'async | |||
| :type '(repeat (choice symbol))) | |||
| (defvar async-byte-compile-log-file "~/.emacs.d/async-bytecomp.log") | |||
| ;;;###autoload | |||
| (defun async-byte-recompile-directory (directory &optional quiet) | |||
| "Compile all *.el files in DIRECTORY asynchronously. | |||
| All *.elc files are systematically deleted before proceeding." | |||
| (cl-loop with dir = (directory-files directory t "\\.elc\\'") | |||
| unless dir return nil | |||
| for f in dir | |||
| when (file-exists-p f) do (delete-file f)) | |||
| ;; Ensure async is reloaded when async.elc is deleted. | |||
| ;; This happen when recompiling its own directory. | |||
| (load "async") | |||
| (let ((call-back | |||
| `(lambda (&optional ignore) | |||
| (if (file-exists-p async-byte-compile-log-file) | |||
| (let ((buf (get-buffer-create byte-compile-log-buffer)) | |||
| (n 0)) | |||
| (with-current-buffer buf | |||
| (goto-char (point-max)) | |||
| (let ((inhibit-read-only t)) | |||
| (insert-file-contents async-byte-compile-log-file) | |||
| (compilation-mode)) | |||
| (display-buffer buf) | |||
| (delete-file async-byte-compile-log-file) | |||
| (unless ,quiet | |||
| (save-excursion | |||
| (goto-char (point-min)) | |||
| (while (re-search-forward "^.*:Error:" nil t) | |||
| (cl-incf n))) | |||
| (if (> n 0) | |||
| (message "Failed to compile %d files in directory `%s'" n ,directory) | |||
| (message "Directory `%s' compiled asynchronously with warnings" ,directory))))) | |||
| (unless ,quiet | |||
| (message "Directory `%s' compiled asynchronously with success" ,directory)))))) | |||
| (async-start | |||
| `(lambda () | |||
| (require 'bytecomp) | |||
| ,(async-inject-variables "\\`\\(load-path\\)\\|byte\\'") | |||
| (let ((default-directory (file-name-as-directory ,directory)) | |||
| error-data) | |||
| (add-to-list 'load-path default-directory) | |||
| (byte-recompile-directory ,directory 0 t) | |||
| (when (get-buffer byte-compile-log-buffer) | |||
| (setq error-data (with-current-buffer byte-compile-log-buffer | |||
| (buffer-substring-no-properties (point-min) (point-max)))) | |||
| (unless (string= error-data "") | |||
| (with-temp-file ,async-byte-compile-log-file | |||
| (erase-buffer) | |||
| (insert error-data)))))) | |||
| call-back) | |||
| (unless quiet (message "Started compiling asynchronously directory %s" directory)))) | |||
| (defvar package-archive-contents) | |||
| (defvar package-alist) | |||
| (declare-function package-desc-reqs "package.el" (cl-x)) | |||
| (defun async-bytecomp--get-package-deps (pkg &optional only) | |||
| ;; Same as `package--get-deps' but parse instead `package-archive-contents' | |||
| ;; because PKG is not already installed and not present in `package-alist'. | |||
| ;; However fallback to `package-alist' in case PKG no more present | |||
| ;; in `package-archive-contents' due to modification to `package-archives'. | |||
| ;; See issue #58. | |||
| (let* ((pkg-desc (cadr (or (assq pkg package-archive-contents) | |||
| (assq pkg package-alist)))) | |||
| (direct-deps (cl-loop for p in (package-desc-reqs pkg-desc) | |||
| for name = (car p) | |||
| when (or (assq name package-archive-contents) | |||
| (assq name package-alist)) | |||
| collect name)) | |||
| (indirect-deps (unless (eq only 'direct) | |||
| (delete-dups | |||
| (cl-loop for p in direct-deps append | |||
| (async-bytecomp--get-package-deps p)))))) | |||
| (cl-case only | |||
| (direct direct-deps) | |||
| (separate (list direct-deps indirect-deps)) | |||
| (indirect indirect-deps) | |||
| (t (delete-dups (append direct-deps indirect-deps)))))) | |||
| (defun async-bytecomp-get-allowed-pkgs () | |||
| (when (and async-bytecomp-allowed-packages | |||
| (listp async-bytecomp-allowed-packages)) | |||
| (if package-archive-contents | |||
| (cl-loop for p in async-bytecomp-allowed-packages | |||
| when (assq p package-archive-contents) | |||
| append (async-bytecomp--get-package-deps p) into reqs | |||
| finally return | |||
| (delete-dups | |||
| (append async-bytecomp-allowed-packages reqs))) | |||
| async-bytecomp-allowed-packages))) | |||
| (defadvice package--compile (around byte-compile-async) | |||
| (let ((cur-package (package-desc-name pkg-desc)) | |||
| (pkg-dir (package-desc-dir pkg-desc))) | |||
| (if (or (equal async-bytecomp-allowed-packages '(all)) | |||
| (memq cur-package (async-bytecomp-get-allowed-pkgs))) | |||
| (progn | |||
| (when (eq cur-package 'async) | |||
| (fmakunbound 'async-byte-recompile-directory)) | |||
| ;; Add to `load-path' the latest version of async and | |||
| ;; reload it when reinstalling async. | |||
| (when (string= cur-package "async") | |||
| (cl-pushnew pkg-dir load-path) | |||
| (load "async-bytecomp")) | |||
| ;; `async-byte-recompile-directory' will add directory | |||
| ;; as needed to `load-path'. | |||
| (async-byte-recompile-directory (package-desc-dir pkg-desc) t)) | |||
| ad-do-it))) | |||
| ;;;###autoload | |||
| (define-minor-mode async-bytecomp-package-mode | |||
| "Byte compile asynchronously packages installed with package.el. | |||
| Async compilation of packages can be controlled by | |||
| `async-bytecomp-allowed-packages'." | |||
| :group 'async | |||
| :global t | |||
| (if async-bytecomp-package-mode | |||
| (ad-activate 'package--compile) | |||
| (ad-deactivate 'package--compile))) | |||
| (provide 'async-bytecomp) | |||
| ;;; async-bytecomp.el ends here | |||
| @ -0,0 +1,6 @@ | |||
| (define-package "async" "20160108.1249" "Asynchronous processing in Emacs" 'nil :keywords | |||
| '("async") | |||
| :url "http://elpa.gnu.org/packages/async.html") | |||
| ;; Local Variables: | |||
| ;; no-byte-compile: t | |||
| ;; End: | |||
| @ -0,0 +1,303 @@ | |||
| ;;; async.el --- Asynchronous processing in Emacs | |||
| ;; Copyright (C) 2012-2016 Free Software Foundation, Inc. | |||
| ;; Author: John Wiegley <jwiegley@gmail.com> | |||
| ;; Created: 18 Jun 2012 | |||
| ;; Version: 1.6 | |||
| ;; Keywords: async | |||
| ;; X-URL: https://github.com/jwiegley/emacs-async | |||
| ;; 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 2, 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 GNU Emacs; see the file COPYING. If not, write to the | |||
| ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, | |||
| ;; Boston, MA 02111-1307, USA. | |||
| ;;; Commentary: | |||
| ;; Adds the ability to call asynchronous functions and process with ease. See | |||
| ;; the documentation for `async-start' and `async-start-process'. | |||
| ;;; Code: | |||
| (defgroup async nil | |||
| "Simple asynchronous processing in Emacs" | |||
| :group 'emacs) | |||
| (defvar async-debug nil) | |||
| (defvar async-send-over-pipe t) | |||
| (defvar async-in-child-emacs nil) | |||
| (defvar async-callback nil) | |||
| (defvar async-callback-for-process nil) | |||
| (defvar async-callback-value nil) | |||
| (defvar async-callback-value-set nil) | |||
| (defvar async-current-process nil) | |||
| (defvar async--procvar nil) | |||
| (defun async-inject-variables | |||
| (include-regexp &optional predicate exclude-regexp) | |||
| "Return a `setq' form that replicates part of the calling environment. | |||
| It sets the value for every variable matching INCLUDE-REGEXP and | |||
| also PREDICATE. It will not perform injection for any variable | |||
| matching EXCLUDE-REGEXP (if present). It is intended to be used | |||
| as follows: | |||
| (async-start | |||
| `(lambda () | |||
| (require 'smtpmail) | |||
| (with-temp-buffer | |||
| (insert ,(buffer-substring-no-properties (point-min) (point-max))) | |||
| ;; Pass in the variable environment for smtpmail | |||
| ,(async-inject-variables \"\\`\\(smtpmail\\|\\(user-\\)?mail\\)-\") | |||
| (smtpmail-send-it))) | |||
| 'ignore)" | |||
| `(setq | |||
| ,@(let (bindings) | |||
| (mapatoms | |||
| (lambda (sym) | |||
| (if (and (boundp sym) | |||
| (or (null include-regexp) | |||
| (string-match include-regexp (symbol-name sym))) | |||
| (not (string-match | |||
| (or exclude-regexp "-syntax-table\\'") | |||
| (symbol-name sym)))) | |||
| (let ((value (symbol-value sym))) | |||
| (when (or (null predicate) | |||
| (funcall predicate sym)) | |||
| (setq bindings (cons `(quote ,value) bindings) | |||
| bindings (cons sym bindings))))))) | |||
| bindings))) | |||
| (defalias 'async-inject-environment 'async-inject-variables) | |||
| (defun async-handle-result (func result buf) | |||
| (if (null func) | |||
| (progn | |||
| (set (make-local-variable 'async-callback-value) result) | |||
| (set (make-local-variable 'async-callback-value-set) t)) | |||
| (unwind-protect | |||
| (if (and (listp result) | |||
| (eq 'async-signal (nth 0 result))) | |||
| (signal (car (nth 1 result)) | |||
| (cdr (nth 1 result))) | |||
| (funcall func result)) | |||
| (unless async-debug | |||
| (kill-buffer buf))))) | |||
| (defun async-when-done (proc &optional change) | |||
| "Process sentinal used to retrieve the value from the child process." | |||
| (when (eq 'exit (process-status proc)) | |||
| (with-current-buffer (process-buffer proc) | |||
| (let ((async-current-process proc)) | |||
| (if (= 0 (process-exit-status proc)) | |||
| (if async-callback-for-process | |||
| (if async-callback | |||
| (prog1 | |||
| (funcall async-callback proc) | |||
| (unless async-debug | |||
| (kill-buffer (current-buffer)))) | |||
| (set (make-local-variable 'async-callback-value) proc) | |||
| (set (make-local-variable 'async-callback-value-set) t)) | |||
| (goto-char (point-max)) | |||
| (backward-sexp) | |||
| (async-handle-result async-callback (read (current-buffer)) | |||
| (current-buffer))) | |||
| (set (make-local-variable 'async-callback-value) | |||
| (list 'error | |||
| (format "Async process '%s' failed with exit code %d" | |||
| (process-name proc) (process-exit-status proc)))) | |||
| (set (make-local-variable 'async-callback-value-set) t)))))) | |||
| (defun async--receive-sexp (&optional stream) | |||
| (let ((sexp (decode-coding-string (base64-decode-string | |||
| (read stream)) 'utf-8-unix)) | |||
| ;; Parent expects UTF-8 encoded text. | |||
| (coding-system-for-write 'utf-8-unix)) | |||
| (if async-debug | |||
| (message "Received sexp {{{%s}}}" (pp-to-string sexp))) | |||
| (setq sexp (read sexp)) | |||
| (if async-debug | |||
| (message "Read sexp {{{%s}}}" (pp-to-string sexp))) | |||
| (eval sexp))) | |||
| (defun async--insert-sexp (sexp) | |||
| (let (print-level | |||
| print-length | |||
| (print-escape-nonascii t) | |||
| (print-circle t)) | |||
| (prin1 sexp (current-buffer)) | |||
| ;; Just in case the string we're sending might contain EOF | |||
| (encode-coding-region (point-min) (point-max) 'utf-8-unix) | |||
| (base64-encode-region (point-min) (point-max) t) | |||
| (goto-char (point-min)) (insert ?\") | |||
| (goto-char (point-max)) (insert ?\" ?\n))) | |||
| (defun async--transmit-sexp (process sexp) | |||
| (with-temp-buffer | |||
| (if async-debug | |||
| (message "Transmitting sexp {{{%s}}}" (pp-to-string sexp))) | |||
| (async--insert-sexp sexp) | |||
| (process-send-region process (point-min) (point-max)))) | |||
| (defun async-batch-invoke () | |||
| "Called from the child Emacs process' command-line." | |||
| ;; Make sure 'message' and 'prin1' encode stuff in UTF-8, as parent | |||
| ;; process expects. | |||
| (let ((coding-system-for-write 'utf-8-unix)) | |||
| (setq async-in-child-emacs t | |||
| debug-on-error async-debug) | |||
| (if debug-on-error | |||
| (prin1 (funcall | |||
| (async--receive-sexp (unless async-send-over-pipe | |||
| command-line-args-left)))) | |||
| (condition-case err | |||
| (prin1 (funcall | |||
| (async--receive-sexp (unless async-send-over-pipe | |||
| command-line-args-left)))) | |||
| (error | |||
| (prin1 (list 'async-signal err))))))) | |||
| (defun async-ready (future) | |||
| "Query a FUTURE to see if the ready is ready -- i.e., if no blocking | |||
| would result from a call to `async-get' on that FUTURE." | |||
| (and (memq (process-status future) '(exit signal)) | |||
| (with-current-buffer (process-buffer future) | |||
| async-callback-value-set))) | |||
| (defun async-wait (future) | |||
| "Wait for FUTURE to become ready." | |||
| (while (not (async-ready future)) | |||
| (sit-for 0.05))) | |||
| (defun async-get (future) | |||
| "Get the value from an asynchronously function when it is ready. | |||
| FUTURE is returned by `async-start' or `async-start-process' when | |||
| its FINISH-FUNC is nil." | |||
| (async-wait future) | |||
| (with-current-buffer (process-buffer future) | |||
| (async-handle-result #'identity async-callback-value (current-buffer)))) | |||
| (defun async-message-p (value) | |||
| "Return true of VALUE is an async.el message packet." | |||
| (and (listp value) | |||
| (plist-get value :async-message))) | |||
| (defun async-send (&rest args) | |||
| "Send the given messages to the asychronous Emacs PROCESS." | |||
| (let ((args (append args '(:async-message t)))) | |||
| (if async-in-child-emacs | |||
| (if async-callback | |||
| (funcall async-callback args)) | |||
| (async--transmit-sexp (car args) (list 'quote (cdr args)))))) | |||
| (defun async-receive (&rest args) | |||
| "Send the given messages to the asychronous Emacs PROCESS." | |||
| (async--receive-sexp)) | |||
| ;;;###autoload | |||
| (defun async-start-process (name program finish-func &rest program-args) | |||
| "Start the executable PROGRAM asynchronously. See `async-start'. | |||
| PROGRAM is passed PROGRAM-ARGS, calling FINISH-FUNC with the | |||
| process object when done. If FINISH-FUNC is nil, the future | |||
| object will return the process object when the program is | |||
| finished. Set DEFAULT-DIRECTORY to change PROGRAM's current | |||
| working directory." | |||
| (let* ((buf (generate-new-buffer (concat "*" name "*"))) | |||
| (proc (let ((process-connection-type nil)) | |||
| (apply #'start-process name buf program program-args)))) | |||
| (with-current-buffer buf | |||
| (set (make-local-variable 'async-callback) finish-func) | |||
| (set-process-sentinel proc #'async-when-done) | |||
| (unless (string= name "emacs") | |||
| (set (make-local-variable 'async-callback-for-process) t)) | |||
| proc))) | |||
| ;;;###autoload | |||
| (defun async-start (start-func &optional finish-func) | |||
| "Execute START-FUNC (often a lambda) in a subordinate Emacs process. | |||
| When done, the return value is passed to FINISH-FUNC. Example: | |||
| (async-start | |||
| ;; What to do in the child process | |||
| (lambda () | |||
| (message \"This is a test\") | |||
| (sleep-for 3) | |||
| 222) | |||
| ;; What to do when it finishes | |||
| (lambda (result) | |||
| (message \"Async process done, result should be 222: %s\" | |||
| result))) | |||
| If FINISH-FUNC is nil or missing, a future is returned that can | |||
| be inspected using `async-get', blocking until the value is | |||
| ready. Example: | |||
| (let ((proc (async-start | |||
| ;; What to do in the child process | |||
| (lambda () | |||
| (message \"This is a test\") | |||
| (sleep-for 3) | |||
| 222)))) | |||
| (message \"I'm going to do some work here\") ;; .... | |||
| (message \"Waiting on async process, result should be 222: %s\" | |||
| (async-get proc))) | |||
| If you don't want to use a callback, and you don't care about any | |||
| return value form the child process, pass the `ignore' symbol as | |||
| the second argument (if you don't, and never call `async-get', it | |||
| will leave *emacs* process buffers hanging around): | |||
| (async-start | |||
| (lambda () | |||
| (delete-file \"a remote file on a slow link\" nil)) | |||
| 'ignore) | |||
| Note: Even when FINISH-FUNC is present, a future is still | |||
| returned except that it yields no value (since the value is | |||
| passed to FINISH-FUNC). Call `async-get' on such a future always | |||
| returns nil. It can still be useful, however, as an argument to | |||
| `async-ready' or `async-wait'." | |||
| (let ((sexp start-func) | |||
| ;; Subordinate Emacs will send text encoded in UTF-8. | |||
| (coding-system-for-read 'utf-8-unix)) | |||
| (setq async--procvar | |||
| (async-start-process | |||
| "emacs" (file-truename | |||
| (expand-file-name invocation-name | |||
| invocation-directory)) | |||
| finish-func | |||
| "-Q" "-l" | |||
| ;; Using `locate-library' ensure we use the right file | |||
| ;; when the .elc have been deleted. | |||
| (locate-library "async") | |||
| "-batch" "-f" "async-batch-invoke" | |||
| (if async-send-over-pipe | |||
| "<none>" | |||
| (with-temp-buffer | |||
| (async--insert-sexp (list 'quote sexp)) | |||
| (buffer-string))))) | |||
| (if async-send-over-pipe | |||
| (async--transmit-sexp async--procvar (list 'quote sexp))) | |||
| async--procvar)) | |||
| (defmacro async-sandbox(func) | |||
| "Evaluate FUNC in a separate Emacs process, synchronously." | |||
| `(async-get (async-start ,func))) | |||
| (provide 'async) | |||
| ;;; async.el ends here | |||
| @ -0,0 +1,290 @@ | |||
| ;;; dired-async.el --- Copy/move/delete asynchronously in dired. | |||
| ;; Copyright (C) 2012-2016 Free Software Foundation, Inc. | |||
| ;; Authors: John Wiegley <jwiegley@gmail.com> | |||
| ;; Thierry Volpiatto <thierry.volpiatto@gmail.com> | |||
| ;; Keywords: dired async network | |||
| ;; X-URL: https://github.com/jwiegley/dired-async | |||
| ;; 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 2, 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 GNU Emacs; see the file COPYING. If not, write to the | |||
| ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, | |||
| ;; Boston, MA 02111-1307, USA. | |||
| ;;; Commentary: | |||
| ;; This file provide a redefinition of `dired-create-file' function, | |||
| ;; performs copies, moves and all what is handled by `dired-create-file' | |||
| ;; in the background using a slave Emacs process, | |||
| ;; by means of the async.el module. | |||
| ;; To use it, put this in your .emacs: | |||
| ;; (dired-async-mode 1) | |||
| ;; This will enable async copy/rename etc... | |||
| ;; in dired and helm. | |||
| ;;; Code: | |||
| (require 'cl-lib) | |||
| (require 'dired-aux) | |||
| (require 'async) | |||
| (eval-when-compile | |||
| (defvar async-callback)) | |||
| (defvar dired-async-operation nil) | |||
| (defgroup dired-async nil | |||
| "Copy rename files asynchronously from dired." | |||
| :group 'dired) | |||
| (defcustom dired-async-env-variables-regexp | |||
| "\\`\\(tramp-\\(default\\|connection\\|remote\\)\\|ange-ftp\\)-.*" | |||
| "Variables matching this regexp will be loaded on Child Emacs." | |||
| :type 'regexp | |||
| :group 'dired-async) | |||
| (defcustom dired-async-message-function 'dired-async-mode-line-message | |||
| "Function to use to notify result when operation finish. | |||
| Should take same args as `message'." | |||
| :group 'dired-async | |||
| :type 'function) | |||
| (defcustom dired-async-log-file "/tmp/dired-async.log" | |||
| "File use to communicate errors from Child Emacs to host Emacs." | |||
| :group 'dired-async | |||
| :type 'string) | |||
| (defface dired-async-message | |||
| '((t (:foreground "yellow"))) | |||
| "Face used for mode-line message." | |||
| :group 'dired-async) | |||
| (defface dired-async-mode-message | |||
| '((t (:foreground "Gold"))) | |||
| "Face used for `dired-async--modeline-mode' lighter." | |||
| :group 'dired-async) | |||
| (define-minor-mode dired-async--modeline-mode | |||
| "Notify mode-line that an async process run." | |||
| :group 'dired-async | |||
| :global t | |||
| :lighter (:eval (propertize (format " [%s Async job(s) running]" | |||
| (length (dired-async-processes))) | |||
| 'face 'dired-async-mode-message)) | |||
| (unless dired-async--modeline-mode | |||
| (let ((visible-bell t)) (ding)))) | |||
| (defun dired-async-mode-line-message (text &rest args) | |||
| "Notify end of operation in `mode-line'." | |||
| (message nil) | |||
| (let ((mode-line-format (concat | |||
| " " (propertize | |||
| (if args | |||
| (apply #'format text args) | |||
| text) | |||
| 'face 'dired-async-message)))) | |||
| (force-mode-line-update) | |||
| (sit-for 3) | |||
| (force-mode-line-update))) | |||
| (defun dired-async-processes () | |||
| (cl-loop for p in (process-list) | |||
| when (cl-loop for c in (process-command p) thereis | |||
| (string= "async-batch-invoke" c)) | |||
| collect p)) | |||
| (defun dired-async-kill-process () | |||
| (interactive) | |||
| (let* ((processes (dired-async-processes)) | |||
| (proc (car (last processes)))) | |||
| (delete-process proc) | |||
| (unless (> (length processes) 1) | |||
| (dired-async--modeline-mode -1)))) | |||
| (defun dired-async-after-file-create (len-flist) | |||
| "Callback function used for operation handled by `dired-create-file'." | |||
| (unless (dired-async-processes) | |||
| ;; Turn off mode-line notification | |||
| ;; only when last process end. | |||
| (dired-async--modeline-mode -1)) | |||
| (when dired-async-operation | |||
| (if (file-exists-p dired-async-log-file) | |||
| (progn | |||
| (pop-to-buffer (get-buffer-create "*dired async*")) | |||
| (erase-buffer) | |||
| (insert "Error: ") | |||
| (insert-file-contents dired-async-log-file) | |||
| (delete-file dired-async-log-file)) | |||
| (run-with-timer | |||
| 0.1 nil | |||
| dired-async-message-function "Asynchronous %s of %s file(s) on %s file(s) done" | |||
| (car dired-async-operation) (cadr dired-async-operation) len-flist)))) | |||
| (defun dired-async-maybe-kill-ftp () | |||
| "Return a form to kill ftp process in child emacs." | |||
| (quote | |||
| (progn | |||
| (require 'cl-lib) | |||
| (let ((buf (cl-loop for b in (buffer-list) | |||
| thereis (and (string-match | |||
| "\\`\\*ftp.*" | |||
| (buffer-name b)) b)))) | |||
| (when buf (kill-buffer buf)))))) | |||
| (defun dired-async-create-files (file-creator operation fn-list name-constructor | |||
| &optional marker-char) | |||
| "Same as `dired-create-files' but asynchronous. | |||
| See `dired-create-files' for the behavior of arguments." | |||
| (setq dired-async-operation nil) | |||
| (let (dired-create-files-failures | |||
| failures async-fn-list | |||
| skipped (success-count 0) | |||
| (total (length fn-list)) | |||
| callback) | |||
| (let (to overwrite-query | |||
| overwrite-backup-query) ; for dired-handle-overwrite | |||
| (dolist (from fn-list) | |||
| (setq to (funcall name-constructor from)) | |||
| (if (equal to from) | |||
| (progn | |||
| (setq to nil) | |||
| (dired-log "Cannot %s to same file: %s\n" | |||
| (downcase operation) from))) | |||
| (if (not to) | |||
| (setq skipped (cons (dired-make-relative from) skipped)) | |||
| (let* ((overwrite (file-exists-p to)) | |||
| (dired-overwrite-confirmed ; for dired-handle-overwrite | |||
| (and overwrite | |||
| (let ((help-form '(format "\ | |||
| Type SPC or `y' to overwrite file `%s', | |||
| DEL or `n' to skip to next, | |||
| ESC or `q' to not overwrite any of the remaining files, | |||
| `!' to overwrite all remaining files with no more questions." to))) | |||
| (dired-query 'overwrite-query | |||
| "Overwrite `%s'?" to)))) | |||
| ;; must determine if FROM is marked before file-creator | |||
| ;; gets a chance to delete it (in case of a move). | |||
| (actual-marker-char | |||
| (cond ((integerp marker-char) marker-char) | |||
| (marker-char (dired-file-marker from)) ; slow | |||
| (t nil)))) | |||
| ;; Handle the `dired-copy-file' file-creator specially | |||
| ;; When copying a directory to another directory or | |||
| ;; possibly to itself or one of its subdirectories. | |||
| ;; e.g "~/foo/" => "~/test/" | |||
| ;; or "~/foo/" =>"~/foo/" | |||
| ;; or "~/foo/ => ~/foo/bar/") | |||
| ;; In this case the 'name-constructor' have set the destination | |||
| ;; TO to "~/test/foo" because the old emacs23 behavior | |||
| ;; of `copy-directory' was to not create the subdirectory | |||
| ;; and instead copy the contents. | |||
| ;; With the new behavior of `copy-directory' | |||
| ;; (similar to the `cp' shell command) we don't | |||
| ;; need such a construction of the target directory, | |||
| ;; so modify the destination TO to "~/test/" instead of "~/test/foo/". | |||
| (let ((destname (file-name-directory to))) | |||
| (when (and (file-directory-p from) | |||
| (file-directory-p to) | |||
| (eq file-creator 'dired-copy-file)) | |||
| (setq to destname)) | |||
| ;; If DESTNAME is a subdirectory of FROM, not a symlink, | |||
| ;; and the method in use is copying, signal an error. | |||
| (and (eq t (car (file-attributes destname))) | |||
| (eq file-creator 'dired-copy-file) | |||
| (file-in-directory-p destname from) | |||
| (error "Cannot copy `%s' into its subdirectory `%s'" | |||
| from to))) | |||
| (if overwrite | |||
| (or (and dired-overwrite-confirmed | |||
| (push (cons from to) async-fn-list)) | |||
| (progn | |||
| (push (dired-make-relative from) failures) | |||
| (dired-log "%s `%s' to `%s' failed" | |||
| operation from to))) | |||
| (push (cons from to) async-fn-list))))) | |||
| (setq callback | |||
| `(lambda (&optional ignore) | |||
| (dired-async-after-file-create ,total) | |||
| (when (string= ,(downcase operation) "rename") | |||
| (cl-loop for (file . to) in ',async-fn-list | |||
| do (and (get-file-buffer file) | |||
| (with-current-buffer (get-file-buffer file) | |||
| (set-visited-file-name to nil t)))))))) | |||
| ;; Handle error happening in host emacs. | |||
| (cond | |||
| (dired-create-files-failures | |||
| (setq failures (nconc failures dired-create-files-failures)) | |||
| (dired-log-summary | |||
| (format "%s failed for %d file%s in %d requests" | |||
| operation (length failures) | |||
| (dired-plural-s (length failures)) | |||
| total) | |||
| failures)) | |||
| (failures | |||
| (dired-log-summary | |||
| (format "%s failed for %d of %d file%s" | |||
| operation (length failures) | |||
| total (dired-plural-s total)) | |||
| failures)) | |||
| (skipped | |||
| (dired-log-summary | |||
| (format "%s: %d of %d file%s skipped" | |||
| operation (length skipped) total | |||
| (dired-plural-s total)) | |||
| skipped)) | |||
| (t (message "%s: %s file%s" | |||
| operation success-count (dired-plural-s success-count)))) | |||
| ;; Start async process. | |||
| (when async-fn-list | |||
| (async-start `(lambda () | |||
| (require 'cl-lib) (require 'dired-aux) (require 'dired-x) | |||
| ,(async-inject-variables dired-async-env-variables-regexp) | |||
| (condition-case err | |||
| (let ((dired-recursive-copies (quote always))) | |||
| (cl-loop for (f . d) in (quote ,async-fn-list) | |||
| do (funcall (quote ,file-creator) f d t))) | |||
| (file-error | |||
| (with-temp-file ,dired-async-log-file | |||
| (insert (format "%S" err))))) | |||
| ,(dired-async-maybe-kill-ftp)) | |||
| callback) | |||
| ;; Run mode-line notifications while process running. | |||
| (dired-async--modeline-mode 1) | |||
| (setq dired-async-operation (list operation (length async-fn-list))) | |||
| (message "%s proceeding asynchronously..." operation)))) | |||
| (defadvice dired-create-files (around dired-async) | |||
| (dired-async-create-files file-creator operation fn-list | |||
| name-constructor marker-char)) | |||
| ;;;###autoload | |||
| (define-minor-mode dired-async-mode | |||
| "Do dired actions asynchronously." | |||
| :group 'dired-async | |||
| :global t | |||
| (if dired-async-mode | |||
| (if (fboundp 'advice-add) | |||
| (advice-add 'dired-create-files :override #'dired-async-create-files) | |||
| (ad-activate 'dired-create-files)) | |||
| (if (fboundp 'advice-remove) | |||
| (advice-remove 'dired-create-files #'dired-async-create-files) | |||
| (ad-deactivate 'dired-create-files)))) | |||
| (provide 'dired-async) | |||
| ;;; dired-async.el ends here | |||
| @ -0,0 +1,73 @@ | |||
| ;;; smtpmail-async.el --- Send e-mail with smtpmail.el asynchronously | |||
| ;; Copyright (C) 2012-2016 Free Software Foundation, Inc. | |||
| ;; Author: John Wiegley <jwiegley@gmail.com> | |||
| ;; Created: 18 Jun 2012 | |||
| ;; Keywords: email async | |||
| ;; X-URL: https://github.com/jwiegley/emacs-async | |||
| ;; 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 2, 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 GNU Emacs; see the file COPYING. If not, write to the | |||
| ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, | |||
| ;; Boston, MA 02111-1307, USA. | |||
| ;;; Commentary: | |||
| ;; Send e-mail with smtpmail.el asynchronously. To use: | |||
| ;; | |||
| ;; (require 'smtpmail-async) | |||
| ;; | |||
| ;; (setq send-mail-function 'async-smtpmail-send-it | |||
| ;; message-send-mail-function 'async-smtpmail-send-it) | |||
| ;; | |||
| ;; This assumes you already have smtpmail.el working. | |||
| ;;; Code: | |||
| (defgroup smtpmail-async nil | |||
| "Send e-mail with smtpmail.el asynchronously" | |||
| :group 'smptmail) | |||
| (require 'async) | |||
| (require 'smtpmail) | |||
| (require 'message) | |||
| (defvar async-smtpmail-before-send-hook nil | |||
| "Hook running in the child emacs in `async-smtpmail-send-it'. | |||
| It is called just before calling `smtpmail-send-it'.") | |||
| (defun async-smtpmail-send-it () | |||
| (let ((to (message-field-value "To")) | |||
| (buf-content (buffer-substring-no-properties | |||
| (point-min) (point-max)))) | |||
| (message "Delivering message to %s..." to) | |||
| (async-start | |||
| `(lambda () | |||
| (require 'smtpmail) | |||
| (with-temp-buffer | |||
| (insert ,buf-content) | |||
| (set-buffer-multibyte nil) | |||
| ;; Pass in the variable environment for smtpmail | |||
| ,(async-inject-variables | |||
| "\\`\\(smtpmail\\|async-smtpmail\\|\\(user-\\)?mail\\)-\\|auth-sources\\|epg" | |||
| nil "\\`\\(mail-header-format-function\\|smtpmail-address-buffer\\|mail-mode-abbrev-table\\)") | |||
| (run-hooks 'async-smtpmail-before-send-hook) | |||
| (smtpmail-send-it))) | |||
| `(lambda (&optional ignore) | |||
| (message "Delivering message to %s...done" ,to))))) | |||
| (provide 'smtpmail-async) | |||
| ;;; smtpmail-async.el ends here | |||
| @ -1,64 +0,0 @@ | |||
| ;;; auto-complete-autoloads.el --- automatically extracted autoloads | |||
| ;; | |||
| ;;; Code: | |||
| (add-to-list 'load-path (or (file-name-directory #$) (car load-path))) | |||
| ;;;### (autoloads nil "auto-complete" "auto-complete.el" (21898 47983 | |||
| ;;;;;; 0 0)) | |||
| ;;; Generated autoloads from auto-complete.el | |||
| (autoload 'auto-complete "auto-complete" "\ | |||
| Start auto-completion at current point. | |||
| \(fn &optional SOURCES)" t nil) | |||
| (autoload 'auto-complete-mode "auto-complete" "\ | |||
| AutoComplete mode | |||
| \(fn &optional ARG)" t nil) | |||
| (defvar global-auto-complete-mode nil "\ | |||
| Non-nil if Global-Auto-Complete mode is enabled. | |||
| See the command `global-auto-complete-mode' for a description of this minor mode. | |||
| Setting this variable directly does not take effect; | |||
| either customize it (see the info node `Easy Customization') | |||
| or call the function `global-auto-complete-mode'.") | |||
| (custom-autoload 'global-auto-complete-mode "auto-complete" nil) | |||
| (autoload 'global-auto-complete-mode "auto-complete" "\ | |||
| Toggle Auto-Complete mode in all buffers. | |||
| With prefix ARG, enable Global-Auto-Complete mode if ARG is positive; | |||
| otherwise, disable it. If called from Lisp, enable the mode if | |||
| ARG is omitted or nil. | |||
| Auto-Complete mode is enabled in all buffers where | |||
| `auto-complete-mode-maybe' would do it. | |||
| See `auto-complete-mode' for more information on Auto-Complete mode. | |||
| \(fn &optional ARG)" t nil) | |||
| ;;;*** | |||
| ;;;### (autoloads nil "auto-complete-config" "auto-complete-config.el" | |||
| ;;;;;; (21898 47983 0 0)) | |||
| ;;; Generated autoloads from auto-complete-config.el | |||
| (autoload 'ac-config-default "auto-complete-config" "\ | |||
| \(fn)" nil nil) | |||
| ;;;*** | |||
| ;;;### (autoloads nil nil ("auto-complete-pkg.el") (21898 47983 747280 | |||
| ;;;;;; 0)) | |||
| ;;;*** | |||
| ;; Local Variables: | |||
| ;; version-control: never | |||
| ;; no-byte-compile: t | |||
| ;; no-update-autoloads: t | |||
| ;; End: | |||
| ;;; auto-complete-autoloads.el ends here | |||
| @ -1,6 +0,0 @@ | |||
| (define-package "auto-complete" "20150618.1949" "Auto Completion for GNU Emacs" | |||
| '((popup "0.5.0") | |||
| (cl-lib "0.5"))) | |||
| ;; Local Variables: | |||
| ;; no-byte-compile: t | |||
| ;; End: | |||
| @ -1,380 +0,0 @@ | |||
| ArithmeticError | |||
| AssertionError | |||
| AttributeError | |||
| BaseException | |||
| BufferError | |||
| BytesWarning | |||
| DeprecationWarning | |||
| EOFError | |||
| Ellipsis | |||
| EnvironmentError | |||
| Exception | |||
| False | |||
| FloatingPointError | |||
| FutureWarning | |||
| GeneratorExit | |||
| IOError | |||
| ImportError | |||
| ImportWarning | |||
| IndentationError | |||
| IndexError | |||
| KeyError | |||
| KeyboardInterrupt | |||
| LookupError | |||
| MemoryError | |||
| NameError | |||
| None | |||
| NotImplemented | |||
| NotImplementedError | |||
| OSError | |||
| OverflowError | |||
| PendingDeprecationWarning | |||
| ReferenceError | |||
| RuntimeError | |||
| RuntimeWarning | |||
| StandardError | |||
| StopIteration | |||
| SyntaxError | |||
| SyntaxWarning | |||
| SystemError | |||
| SystemExit | |||
| TabError | |||
| True | |||
| TypeError | |||
| UnboundLocalError | |||
| UnicodeDecodeError | |||
| UnicodeEncodeError | |||
| UnicodeError | |||
| UnicodeTranslateError | |||
| UnicodeWarning | |||
| UserWarning | |||
| ValueError | |||
| Warning | |||
| ZeroDivisionError | |||
| __builtins__ | |||
| __debug__ | |||
| __doc__ | |||
| __file__ | |||
| __future__ | |||
| __import__ | |||
| __init__ | |||
| __main__ | |||
| __name__ | |||
| __package__ | |||
| _dummy_thread | |||
| _thread | |||
| abc | |||
| abs | |||
| aifc | |||
| all | |||
| and | |||
| any | |||
| apply | |||
| argparse | |||
| array | |||
| as | |||
| assert | |||
| ast | |||
| asynchat | |||
| asyncio | |||
| asyncore | |||
| atexit | |||
| audioop | |||
| base64 | |||
| basestring | |||
| bdb | |||
| bin | |||
| binascii | |||
| binhex | |||
| bisect | |||
| bool | |||
| break | |||
| buffer | |||
| builtins | |||
| bytearray | |||
| bytes | |||
| bz2 | |||
| calendar | |||
| callable | |||
| cgi | |||
| cgitb | |||
| chr | |||
| chuck | |||
| class | |||
| classmethod | |||
| cmath | |||
| cmd | |||
| cmp | |||
| code | |||
| codecs | |||
| codeop | |||
| coerce | |||
| collections | |||
| colorsys | |||
| compile | |||
| compileall | |||
| complex | |||
| concurrent | |||
| configparser | |||
| contextlib | |||
| continue | |||
| copy | |||
| copyreg | |||
| copyright | |||
| credits | |||
| crypt | |||
| csv | |||
| ctypes | |||
| curses | |||
| datetime | |||
| dbm | |||
| decimal | |||
| def | |||
| del | |||
| delattr | |||
| dict | |||
| difflib | |||
| dir | |||
| dis | |||
| distutils | |||
| divmod | |||
| doctest | |||
| dummy_threading | |||
| elif | |||
| else | |||
| enumerate | |||
| ensurepip | |||
| enum | |||
| enumerat | |||
| errno | |||
| eval | |||
| except | |||
| exec | |||
| execfile | |||
| exit | |||
| faulthandler | |||
| fcntl | |||
| file | |||
| filecmp | |||
| fileinput | |||
| filter | |||
| finally | |||
| float | |||
| fnmatch | |||
| for | |||
| format | |||
| formatter | |||
| fpectl | |||
| fractions | |||
| from | |||
| frozenset | |||
| ftplib | |||
| functools | |||
| gc | |||
| getattr | |||
| getopt | |||
| getpass | |||
| gettext | |||
| glob | |||
| global | |||
| globals | |||
| grp | |||
| gzip | |||
| hasattr | |||
| hash | |||
| hashlib | |||
| heapq | |||
| help | |||
| hex | |||
| hmac | |||
| html | |||
| http | |||
| id | |||
| if | |||
| imghdr | |||
| imp | |||
| impalib | |||
| import | |||
| importlib | |||
| in | |||
| input | |||
| inspect | |||
| int | |||
| intern | |||
| io | |||
| ipaddress | |||
| is | |||
| isinstance | |||
| issubclass | |||
| iter | |||
| itertools | |||
| json | |||
| keyword | |||
| lambda | |||
| len | |||
| license | |||
| linecache | |||
| list | |||
| locale | |||
| locals | |||
| logging | |||
| long | |||
| lzma | |||
| macpath | |||
| mailbox | |||
| mailcap | |||
| map | |||
| marshal | |||
| math | |||
| max | |||
| memoryview | |||
| mimetypes | |||
| min | |||
| mmap | |||
| modulefinder | |||
| msilib | |||
| msvcrt | |||
| multiprocessing | |||
| netrc | |||
| next | |||
| nis | |||
| nntplib | |||
| not | |||
| numbers | |||
| object | |||
| oct | |||
| open | |||
| operator | |||
| optparse | |||
| or | |||
| ord | |||
| os | |||
| ossaudiodev | |||
| parser | |||
| pass | |||
| pathlib | |||
| pdb | |||
| pickle | |||
| pickletools | |||
| pipes | |||
| pkgutil | |||
| platform | |||
| plistlib | |||
| poplib | |||
| posix | |||
| pow | |||
| pprint | |||
| profile | |||
| property | |||
| pty | |||
| pwd | |||
| py_compiler | |||
| pyclbr | |||
| pydoc | |||
| queue | |||
| quit | |||
| quopri | |||
| raise | |||
| random | |||
| range | |||
| raw_input | |||
| re | |||
| readline | |||
| reduce | |||
| reload | |||
| repr | |||
| reprlib | |||
| resource | |||
| return | |||
| reversed | |||
| rlcompleter | |||
| round | |||
| runpy | |||
| sched | |||
| select | |||
| selectors | |||
| self | |||
| set | |||
| setattr | |||
| shelve | |||
| shlex | |||
| shutil | |||
| signal | |||
| site | |||
| slice | |||
| smtpd | |||
| smtplib | |||
| sndhdr | |||
| socket | |||
| socketserver | |||
| sorted | |||
| spwd | |||
| sqlite3 | |||
| ssl | |||
| stat | |||
| staticmethod | |||
| statistics | |||
| str | |||
| string | |||
| stringprep | |||
| struct | |||
| subprocess | |||
| sum | |||
| sunau | |||
| super | |||
| symbol | |||
| symtable | |||
| sys | |||
| sysconfig | |||
| syslog | |||
| tabnanny | |||
| tarfile | |||
| telnetlib | |||
| tempfile | |||
| termios | |||
| test | |||
| textwrap | |||
| threading | |||
| time | |||
| timeit | |||
| tkinter | |||
| token | |||
| tokenize | |||
| trace | |||
| traceback | |||
| tracemalloc | |||
| try | |||
| tty | |||
| tuple | |||
| turtle | |||
| type | |||
| types | |||
| unichr | |||
| unicode | |||
| unicodedata | |||
| unittest | |||
| urllib | |||
| uu | |||
| uuid | |||
| vars | |||
| venv | |||
| warnings | |||
| wave | |||
| weakref | |||
| webbrowser | |||
| while | |||
| winsound | |||
| winreg | |||
| with | |||
| wsgiref | |||
| xdrlib | |||
| xml | |||
| xmlrpc | |||
| xrange | |||
| yield | |||
| zip | |||
| zipfile | |||
| zipimport | |||
| zlib | |||
| @ -0,0 +1,64 @@ | |||
| ;;; auto-complete-autoloads.el --- automatically extracted autoloads | |||
| ;; | |||
| ;;; Code: | |||
| (add-to-list 'load-path (or (file-name-directory #$) (car load-path))) | |||
| ;;;### (autoloads nil "auto-complete" "auto-complete.el" (22171 46584 | |||
| ;;;;;; 0 0)) | |||
| ;;; Generated autoloads from auto-complete.el | |||
| (autoload 'auto-complete "auto-complete" "\ | |||
| Start auto-completion at current point. | |||
| \(fn &optional SOURCES)" t nil) | |||
| (autoload 'auto-complete-mode "auto-complete" "\ | |||
| AutoComplete mode | |||
| \(fn &optional ARG)" t nil) | |||
| (defvar global-auto-complete-mode nil "\ | |||
| Non-nil if Global-Auto-Complete mode is enabled. | |||
| See the command `global-auto-complete-mode' for a description of this minor mode. | |||
| Setting this variable directly does not take effect; | |||
| either customize it (see the info node `Easy Customization') | |||
| or call the function `global-auto-complete-mode'.") | |||
| (custom-autoload 'global-auto-complete-mode "auto-complete" nil) | |||
| (autoload 'global-auto-complete-mode "auto-complete" "\ | |||
| Toggle Auto-Complete mode in all buffers. | |||
| With prefix ARG, enable Global-Auto-Complete mode if ARG is positive; | |||
| otherwise, disable it. If called from Lisp, enable the mode if | |||
| ARG is omitted or nil. | |||
| Auto-Complete mode is enabled in all buffers where | |||
| `auto-complete-mode-maybe' would do it. | |||
| See `auto-complete-mode' for more information on Auto-Complete mode. | |||
| \(fn &optional ARG)" t nil) | |||
| ;;;*** | |||
| ;;;### (autoloads nil "auto-complete-config" "auto-complete-config.el" | |||
| ;;;;;; (22171 46584 0 0)) | |||
| ;;; Generated autoloads from auto-complete-config.el | |||
| (autoload 'ac-config-default "auto-complete-config" "\ | |||
| \(fn)" nil nil) | |||
| ;;;*** | |||
| ;;;### (autoloads nil nil ("auto-complete-pkg.el") (22171 46584 590372 | |||
| ;;;;;; 0)) | |||
| ;;;*** | |||
| ;; Local Variables: | |||
| ;; version-control: never | |||
| ;; no-byte-compile: t | |||
| ;; no-update-autoloads: t | |||
| ;; End: | |||
| ;;; auto-complete-autoloads.el ends here | |||
| @ -0,0 +1,6 @@ | |||
| (define-package "auto-complete" "20160107.8" "Auto Completion for GNU Emacs" | |||
| '((popup "0.5.0") | |||
| (cl-lib "0.5"))) | |||
| ;; Local Variables: | |||
| ;; no-byte-compile: t | |||
| ;; End: | |||