| @ -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: | |||||