| @ -0,0 +1,50 @@ | |||
| ;;; ac-js2-autoloads.el --- automatically extracted autoloads | |||
| ;; | |||
| ;;; Code: | |||
| (add-to-list 'load-path (or (file-name-directory #$) (car load-path))) | |||
| ;;;### (autoloads nil "ac-js2" "ac-js2.el" (21824 55291 0 0)) | |||
| ;;; Generated autoloads from ac-js2.el | |||
| (autoload 'ac-js2-expand-function "ac-js2" "\ | |||
| Expand the function definition left of point. | |||
| Expansion will only occur for candidates whose documentation | |||
| string contain a function prototype. | |||
| \(fn)" t nil) | |||
| (autoload 'ac-js2-completion-function "ac-js2" "\ | |||
| Function for `completions-at-point'. | |||
| \(fn)" nil nil) | |||
| (autoload 'ac-js2-company "ac-js2" "\ | |||
| \(fn COMMAND &optional ARG &rest IGNORED)" t nil) | |||
| (autoload 'ac-js2-jump-to-definition "ac-js2" "\ | |||
| Jump to the definition of an object's property, variable or function. | |||
| Navigation to a property definend in an Object literal isn't | |||
| implemented. | |||
| \(fn)" t nil) | |||
| (autoload 'ac-js2-mode "ac-js2" "\ | |||
| A minor mode that provides auto-completion and navigation for Js2-mode. | |||
| \(fn &optional ARG)" t nil) | |||
| ;;;*** | |||
| ;;;### (autoloads nil nil ("ac-js2-pkg.el" "ac-js2-tests.el") (21824 | |||
| ;;;;;; 55291 874082 0)) | |||
| ;;;*** | |||
| ;; Local Variables: | |||
| ;; version-control: never | |||
| ;; no-byte-compile: t | |||
| ;; no-update-autoloads: t | |||
| ;; End: | |||
| ;;; ac-js2-autoloads.el ends here | |||
| @ -0,0 +1,7 @@ | |||
| (define-package "ac-js2" "20140906.442" "Auto-complete source for Js2-mode, with navigation" | |||
| '((js2-mode "20090723") | |||
| (skewer-mode "1.4")) | |||
| :url "https://github.com/ScottyB/ac-js2") | |||
| ;; Local Variables: | |||
| ;; no-byte-compile: t | |||
| ;; End: | |||
| @ -0,0 +1,76 @@ | |||
| ;;; Tests for ac-js2 | |||
| (require 'ert) | |||
| (require 'skewer-mode) | |||
| (require 'js2-mode) | |||
| (require 'ac-js2) | |||
| ;;; Must have a skewer client connected before running the tests | |||
| ;; Need to call httpd-stop from main Emacs if running tests in batch mode | |||
| (unless skewer-clients | |||
| (run-skewer)) | |||
| (ert-deftest ac-js2-candidates-test () | |||
| "Test the major function that returns candidates for all frontends." | |||
| (let (property | |||
| property-dot | |||
| func-call | |||
| var) | |||
| (with-temp-buffer | |||
| (insert " | |||
| var temp = function(param1, param2) { | |||
| var localParam = 15; | |||
| return param1 + param2; | |||
| }; | |||
| var look; | |||
| temp.aFun = function(lolParam) {}; | |||
| temp.anotherFunction = function() { return {about: 3};}") | |||
| (setq ac-js2-evaluate-calls t) | |||
| (setq ac-js2-external-libraries nil) | |||
| (js2-mode) | |||
| (ac-js2-mode t) | |||
| (js2-parse) | |||
| (insert "tem") | |||
| (ac-js2-candidates) | |||
| (setq var ac-js2-skewer-candidates) | |||
| (delete-char -3) | |||
| (insert "temp.") | |||
| (js2-parse) | |||
| (ac-js2-candidates) | |||
| (setq property-dot ac-js2-skewer-candidates) | |||
| (delete-char -5) | |||
| (insert "temp.aF") | |||
| (js2-parse) | |||
| (ac-js2-candidates) | |||
| (setq property ac-js2-skewer-candidates)) | |||
| (should (assoc 'anotherFunction property-dot)) | |||
| (print property) | |||
| (should (assoc 'aFun property)) | |||
| (should (assoc 'temp var)))) | |||
| (defmacro completion-frontend-test (test-name completion-function) | |||
| "Utility for testing completion front ends. | |||
| TODO: cover more cases" | |||
| `(ert-deftest ,test-name () | |||
| (let (var) | |||
| (with-temp-buffer | |||
| (insert "var testComplete = function(param1, param2) {};") | |||
| (js2-mode) | |||
| (ac-js2-mode t) | |||
| (js2-parse) | |||
| (insert "testComplet") | |||
| (funcall ',completion-function) | |||
| (setq var (thing-at-point 'word))) | |||
| (should (string= var "testComplete"))))) | |||
| (completion-frontend-test auto-complete-test auto-complete) | |||
| (completion-frontend-test completion-at-point-test completion-at-point) | |||
| @ -0,0 +1,605 @@ | |||
| ;;; ac-js2.el --- Auto-complete source for Js2-mode, with navigation | |||
| ;; Copyright (C) 2013 Scott Barnett | |||
| ;; Author: Scott Barnett <scott.n.barnett@gmail.com> | |||
| ;; URL: https://github.com/ScottyB/ac-js2 | |||
| ;; Version: 1.0 | |||
| ;; Package-Requires: ((js2-mode "20090723")(skewer-mode "1.4")) | |||
| ;; 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: | |||
| ;; An attempt to get context sensitive Javascript completion in Emacs. | |||
| ;; Basic completions are obtained by parsing Javascript code with | |||
| ;; Js2-mode's parser. | |||
| ;; | |||
| ;; Installation | |||
| ;; | |||
| ;; Easiest way to get ac-js2 is to install it from MELPA. You may need | |||
| ;; this snippet | |||
| ;; | |||
| ;; `(add-to-list 'package-archives | |||
| ;; '("melpa" . "http://melpa.milkbox.net/packages/") t)' | |||
| ;; | |||
| ;; if you don't have it already to fetch packages from MELPA. | |||
| ;; | |||
| ;; Enable ac-js2 in js2-mode as follows: | |||
| ;; | |||
| ;; (add-hook 'js2-mode-hook 'ac-js2-mode) | |||
| ;; | |||
| ;; Ac-js2 does not require auto-complete mode but I suggest you grab | |||
| ;; it anyway as ac-js2 is designed to work with a completion frontend. | |||
| ;; Support for Company mode is on its way. | |||
| ;; | |||
| ;; For more comprehensive completions you can opt to evaluate the code | |||
| ;; for candidates. A browser needs to be connected to Emacs for the | |||
| ;; evaluation completions to work. Put this in your init.el file. | |||
| ;; | |||
| ;; `(setq ac-js2-evaluate-calls t)' | |||
| ;; | |||
| ;; To add completions for external libraries add something like this: | |||
| ;; | |||
| ;; (add-to-list 'ac-js2-external-libraries "path/to/lib/library.js") | |||
| ;; | |||
| ;; Then connect a browser to Emacs by calling `(run-skewer)'. You may | |||
| ;; need to save the buffer for completions to start. | |||
| ;; | |||
| ;; If auto-complete mode is installed on your system then completions | |||
| ;; should start showing up otherwise use `completion-at-point'. | |||
| ;; | |||
| ;; Note: library completions will only work if `ac-js2-evaluate-calls' | |||
| ;; is set and a browser is connected to Emacs. | |||
| ;; | |||
| ;; Bonus: M-. is bound to `ac-js2-jump-to-definition' which will jump | |||
| ;; to Javascript definitions found in the same buffer. Given the | |||
| ;; following proprety reference: | |||
| ;; | |||
| ;; foo.bar.baz(); | |||
| ;; | |||
| ;; placing the cursor on `foo', `bar' or `baz' and executing M-. will | |||
| ;; take you straight to their respective definitions. Use M-, to jump | |||
| ;; back to where you were. Also works for object literals. | |||
| ;; | |||
| ;; Recently added `ac-js2-expand-function' that will expand a function's | |||
| ;; parameters bound to `C-c C-c`. Expansion will only work if the cursor | |||
| ;; is after the function. | |||
| ;; | |||
| ;; If you have any issues or suggestions please create an issue on Github: | |||
| ;; https://github.com/ScottyB/ac-js2 | |||
| ;;; History: | |||
| ;; Version 1.0 | |||
| ;; * Navigation within current buffer | |||
| ;; * Completion and docstring for objects via Skewer | |||
| ;; * External library support | |||
| ;; * Basic completions of objects in current buffer | |||
| ;;; Code: | |||
| (require 'js2-mode) | |||
| (require 'skewer-mode) | |||
| (require 'cl-lib) | |||
| (require 'etags) | |||
| (defgroup ac-js2 nil | |||
| "Auto-completion for js2-mode." | |||
| :group 'completion | |||
| :prefix "ac-js2-") | |||
| ;;; Configuration variables | |||
| (defcustom ac-js2-add-ecma-262-externs t | |||
| "If non-nil add `js2-ecma-262-externs' to completion candidates.") | |||
| (defcustom ac-js2-add-browser-externs t | |||
| "If non-nil add `js2-browser-externs' to completion candidates.") | |||
| (defcustom ac-js2-add-keywords t | |||
| "If non-nil add `js2-keywords' to completion candidates.") | |||
| (defcustom ac-js2-add-prototype-completions t | |||
| "When non-nil traverse the prototype chain adding to completion candidates.") | |||
| (defcustom ac-js2-external-libraries '() | |||
| "List of absolute paths to external Javascript libraries.") | |||
| (defcustom ac-js2-evaluate-calls nil | |||
| "Warning. When true function calls will be evaluated in the browser. | |||
| This may cause undesired side effects however it will | |||
| provide better completions. Use at your own risk.") | |||
| (defcustom ac-js2-force-reparse t | |||
| "Force Js2-mode to reparse buffer before fetching completion candidates.") | |||
| ;;; Internal variables | |||
| (defvar ac-js2-keywords '() | |||
| "Cached string version of `js2-keywords'.") | |||
| (defvar ac-js2-candidates '()) | |||
| ;; Types of skewer completion methods available | |||
| (defconst ac-js2-method-eval 0) | |||
| (defconst ac-js2-method-global 1 | |||
| "Return candidates for the global object. | |||
| Only keys of the object are returned as the other properties come | |||
| from js2-mode's externs.") | |||
| (defvar ac-js2-data-root (file-name-directory load-file-name) | |||
| "Location of data files needed for `ac-js2-on-skewer-load'.") | |||
| ;;; Skewer integration | |||
| (defvar ac-js2-skewer-candidates '() | |||
| "Cadidates obtained from skewering.") | |||
| (defun ac-js2-on-skewer-load () | |||
| "Inject skewer addon and evaluate external libraries in browser." | |||
| (insert-file-contents (expand-file-name "skewer-addon.js" ac-js2-data-root)) | |||
| (and ac-js2-evaluate-calls | |||
| (mapcar (lambda (library) | |||
| (with-temp-buffer | |||
| (insert-file-contents (expand-file-name library)) | |||
| (skewer-eval (buffer-string) | |||
| nil | |||
| :type "complete"))) ac-js2-external-libraries))) | |||
| (defun ac-js2-skewer-completion-candidates () | |||
| "Get completions returned from skewer." | |||
| (mapcar (lambda (candidate) (symbol-name (car candidate))) ac-js2-skewer-candidates)) | |||
| (defun ac-js2-skewer-document-candidates (name) | |||
| "Return document string for NAME from skewer." | |||
| (let ((doc (cdr (assoc-string name ac-js2-skewer-candidates)))) | |||
| (or (ac-js2-format-function doc) doc))) | |||
| (defun ac-js2-get-object-properties (name) | |||
| "Find properties of NAME for completion." | |||
| (ac-js2-skewer-eval-wrapper name `((prototypes . ,ac-js2-add-prototype-completions)))) | |||
| (defun ac-js2-skewer-result-callback (result) | |||
| "Process the RESULT passed from the browser." | |||
| (let ((value (cdr (assoc 'value result)))) | |||
| (if (and (skewer-success-p result) value) | |||
| (setq ac-js2-skewer-candidates (append value nil))))) | |||
| (defun ac-js2-skewer-eval-wrapper (str &optional extras) | |||
| "Wrap `skewer-eval-synchronously' to check if a skewer-client is avilable. | |||
| STR is the text to send to the browser for evaluation. Extra | |||
| parameters can be passed to the browser using EXTRAS. EXTRAS must | |||
| be of the form (param-string . value) where param-string is the | |||
| reference and value is the value that can be retrieved from the | |||
| request object in Javacript." | |||
| (setq ac-js2-skewer-candidates nil) | |||
| (if skewer-clients | |||
| (if (or ac-js2-evaluate-calls | |||
| (not (ac-js2-has-function-calls str))) | |||
| (ac-js2-skewer-result-callback | |||
| (skewer-eval-synchronously str | |||
| :type "complete" | |||
| :extra extras))) | |||
| (setq skewer-queue nil))) | |||
| ;; Generate candidates | |||
| (defun ac-js2-candidates () | |||
| "Main function called to gather candidates for auto-completion." | |||
| (if ac-js2-force-reparse (js2-reparse)) | |||
| (let ((node (js2-node-parent (js2-node-at-point (1- (point))))) | |||
| beg | |||
| (prop-get-regex "[a-zA-Z)]\\.") | |||
| name) | |||
| (setq ac-js2-candidates nil) | |||
| (cond | |||
| ((looking-back "\\.") | |||
| ;; TODO: Need to come up with a better way to extract object than this regex!! | |||
| (save-excursion | |||
| (setq beg (and (skip-chars-backward "[a-zA-Z_$][0-9a-zA-Z_$#\"())]+\\.") (point)))) | |||
| (setq name (buffer-substring-no-properties beg (1- (point)))) | |||
| (ac-js2-get-object-properties name) | |||
| (setq node (ac-js2-initialized-node (if (string-match prop-get-regex name) | |||
| (reverse (split-string name prop-get-regex)) name))) | |||
| (if (js2-object-node-p node) | |||
| (setq ac-js2-candidates | |||
| (mapcar (lambda (elem) | |||
| (ac-js2-format-node (js2-node-string (js2-object-prop-node-left elem)) | |||
| elem)) | |||
| (js2-object-node-elems node)))) | |||
| (append (mapcar 'first ac-js2-candidates) | |||
| (ac-js2-skewer-completion-candidates))) | |||
| ((js2-prop-get-node-p node) | |||
| (setq node (js2-prop-get-node-left node)) | |||
| (setq name (js2-node-string node)) | |||
| (ac-js2-get-object-properties name) | |||
| (ac-js2-skewer-completion-candidates)) | |||
| (t | |||
| (ac-js2-skewer-eval-wrapper "" `((method . ,ac-js2-method-global))) | |||
| (append (ac-js2-skewer-completion-candidates) | |||
| (ac-js2-add-extra-completions | |||
| (mapcar 'first (ac-js2-get-names-in-scope)))))))) | |||
| (defun ac-js2-document (name) | |||
| "Show documentation for NAME from local buffer if present | |||
| otherwise use documentation obtained from skewer." | |||
| (let* ((docs (cdr (assoc name ac-js2-candidates))) | |||
| (doc (if (listp docs) (first docs) docs))) | |||
| (if doc doc (ac-js2-skewer-document-candidates name)))) | |||
| ;; Auto-complete settings | |||
| (defun ac-js2-ac-candidates () | |||
| "Completion candidates for auto-complete mode." | |||
| (ac-js2-candidates)) | |||
| (defun ac-js2-ac-document (name) | |||
| "Documentation to be shown for auto-complete mode." | |||
| (ac-js2-document name)) | |||
| (defun ac-js2-ac-prefix() | |||
| (or (ac-prefix-default) (ac-prefix-c-dot))) | |||
| (defun ac-js2-save () | |||
| "Called on `before-save-hook' to evaluate buffer." | |||
| (interactive) | |||
| (when (string= major-mode "js2-mode") | |||
| (ac-js2-skewer-eval-wrapper (buffer-string))) | |||
| t) | |||
| ;;;###autoload | |||
| (defun ac-js2-expand-function() | |||
| "Expand the function definition left of point. | |||
| Expansion will only occur for candidates whose documentation | |||
| string contain a function prototype." | |||
| (interactive) | |||
| (let* ((word (progn | |||
| (if (featurep 'auto-complete) (ac-complete)) | |||
| (substring-no-properties (or (thing-at-point 'word) "")))) | |||
| (candidate (ac-js2-ac-document word))) | |||
| (if (and (looking-back word) (stringp candidate)) | |||
| (when (string-match "^function" candidate) | |||
| (cond ((featurep 'yasnippet) | |||
| (yas-expand-snippet | |||
| (concat "(" | |||
| (replace-regexp-in-string "\\([a-zA-Z0-9]+\\)" | |||
| (lambda (txt) (concat "${" txt "}")) | |||
| (second (split-string candidate "[()]"))) | |||
| ")$0")))))))) | |||
| (defun ac-js2-setup-auto-complete-mode () | |||
| "Setup ac-js2 to be used with auto-complete-mode." | |||
| (add-to-list 'ac-sources 'ac-source-js2) | |||
| (auto-complete-mode) | |||
| (ac-define-source "js2" | |||
| '((candidates . ac-js2-ac-candidates) | |||
| (document . ac-js2-ac-document) | |||
| (prefix . ac-js2-ac-prefix) | |||
| (requires . -1)))) | |||
| ;;; Completion at point function | |||
| ;;;###autoload | |||
| (defun ac-js2-completion-function () | |||
| "Function for `completions-at-point'." | |||
| (save-excursion | |||
| (let ((bounds (if (looking-back "\\.") | |||
| (cons (point) (point)) | |||
| (bounds-of-thing-at-point 'word)))) | |||
| (list (car bounds) (cdr bounds) (ac-js2-candidates))))) | |||
| ;;; Company | |||
| ;;;###autoload | |||
| (defun ac-js2-company (command &optional arg &rest ignored) | |||
| (interactive (list 'interactive)) | |||
| (if (not (featurep 'company)) | |||
| (message "Company is not installed") | |||
| (case command | |||
| (interactive (company-begin-backend 'ac-js2-company)) | |||
| (prefix (when ac-js2-mode | |||
| (or (company-grab-symbol) | |||
| 'stop))) | |||
| (candidates (all-completions arg (ac-js2-candidates))) | |||
| (duplicates t) | |||
| (meta (let ((doc (ac-js2-document arg))) | |||
| (when doc | |||
| (with-temp-buffer | |||
| (insert doc) | |||
| (js-mode) | |||
| (font-lock-ensure) | |||
| (buffer-string)))))))) | |||
| ;;; Helper functions | |||
| (defun ac-js2-build-prop-name-list (prop-node) | |||
| "Build a list of names from a PROP-NODE." | |||
| (let* (names | |||
| left | |||
| left-node) | |||
| (unless (js2-prop-get-node-p prop-node) | |||
| (error "Node is not a property prop-node")) | |||
| (while (js2-prop-get-node-p prop-node) | |||
| (push (js2-name-node-name (js2-prop-get-node-right prop-node)) names) | |||
| (setq left-node (js2-prop-get-node-left prop-node)) | |||
| (when (js2-name-node-p left-node) | |||
| (setq left (js2-name-node-name left-node))) | |||
| (setq prop-node (js2-node-parent prop-node))) | |||
| (append names `(,left)))) | |||
| (defun ac-js2-prop-names-left (name-node) | |||
| "Create a list of all of the names in the property NAME-NODE. | |||
| NAME-NODE must have a js2-prop-get-node as parent. Only adds | |||
| properties to the left of point. This is so individual jump | |||
| points can be found for each property in the chain." | |||
| (let* (name | |||
| (parent (js2-node-parent name-node)) | |||
| left | |||
| names) | |||
| (unless (or (js2-prop-get-node-p parent) (js2-name-node-p name-node)) | |||
| (error "Not a name node or doesn't have a prop-get-node as parent")) | |||
| (setq name (js2-name-node-name name-node) | |||
| left (js2-prop-get-node-left parent)) | |||
| (if (and (js2-name-node-p left) | |||
| (string= name (js2-name-node-name left))) | |||
| (setq names name) | |||
| (js2-visit-ast | |||
| parent | |||
| (lambda (node endp) | |||
| (unless endp | |||
| (if (js2-name-node-p node) | |||
| (push (js2-name-node-name node) names) | |||
| t)))) | |||
| names))) | |||
| (defun ac-js2-has-function-calls (string) | |||
| "Check if the Javascript code in STRING has a Js2-call-node." | |||
| (with-temp-buffer | |||
| (insert string) | |||
| (let* ((ast (js2-parse))) | |||
| (catch 'call-node | |||
| (js2-visit-ast-root | |||
| ast | |||
| (lambda (node end-p) | |||
| (unless end-p | |||
| (if (js2-call-node-p node) | |||
| (throw 'call-node t) | |||
| t)))))))) | |||
| (defun ac-js2-add-extra-completions (completions) | |||
| "Add extra candidates to COMPLETIONS." | |||
| (append completions | |||
| (if ac-js2-add-keywords (or ac-js2-keywords (setq ac-js2-keywords (mapcar 'symbol-name js2-keywords)))) | |||
| (if ac-js2-add-ecma-262-externs js2-ecma-262-externs) | |||
| (if ac-js2-add-browser-externs js2-browser-externs))) | |||
| (defun ac-js2-root-or-node () | |||
| "Return the current node or js2-ast-root node." | |||
| (let ((node (js2-node-at-point))) | |||
| (if (js2-ast-root-p node) | |||
| node | |||
| (js2-node-get-enclosing-scope node)))) | |||
| (defun ac-js2-get-names-in-scope () | |||
| "Fetches all symbols in scope and formats them for completion." | |||
| (let* ((scope (ac-js2-root-or-node)) | |||
| result) | |||
| (while scope | |||
| (setq result (append result | |||
| (loop for item in (js2-scope-symbol-table scope) | |||
| if (not (assoc (car item) result)) | |||
| collect item))) | |||
| (setq scope (js2-scope-parent-scope scope))) | |||
| (setq ac-js2-candidates | |||
| (mapcar #'(lambda (x) | |||
| (let* ((name (symbol-name (car x))) | |||
| (init (ac-js2-initialized-node name))) | |||
| (ac-js2-format-node name init))) | |||
| result)))) | |||
| (defun ac-js2-initialized-node (name) | |||
| "Return initial value assigned to NAME. | |||
| NAME may be either a variable, a function or a variable that | |||
| holds a function. NAME may also be a list of names that make up a | |||
| object property. Returns nil if no initial value can be found." | |||
| (let* ((node (if (listp name) (ac-js2-find-property name) | |||
| (ac-js2-name-declaration name))) | |||
| (parent (if node (js2-node-parent node))) | |||
| (init (cond | |||
| ((js2-function-node-p parent) | |||
| parent) | |||
| ((js2-function-node-p node) | |||
| node) | |||
| ((js2-var-init-node-p parent) | |||
| (js2-var-init-node-initializer parent)) | |||
| ((js2-assign-node-p parent) | |||
| (js2-assign-node-right parent)) | |||
| (t | |||
| nil)))) | |||
| init)) | |||
| (defun ac-js2-name-declaration (name) | |||
| "Return the declaration node for node named NAME." | |||
| (let* ((node (ac-js2-root-or-node)) | |||
| (scope-def (js2-get-defining-scope node name)) | |||
| (scope (if scope-def (js2-scope-get-symbol scope-def name) nil)) | |||
| (symbol (if scope (js2-symbol-ast-node scope) nil))) | |||
| (if (not symbol) | |||
| (ac-js2-get-function-node name scope-def) | |||
| symbol))) | |||
| ;;; Completion candidate formatting | |||
| (defun ac-js2-format-node (name node) | |||
| "Format NAME and NODE for completion. | |||
| Returned format is a list where the first element is the NAME of | |||
| the node (shown in completion candidate list) and the last | |||
| element is the text to show as documentation." | |||
| (let ((node (if (js2-object-prop-node-p node) (js2-object-prop-node-right node) node)) | |||
| (name-format (replace-regexp-in-string "\"" "" name)) | |||
| (doc (if (and (js2-function-node-p node) | |||
| (cl-find name (js2-function-node-params node) | |||
| :test '(lambda (name param) (string= name (js2-name-node-name param))))) | |||
| "Function parameter" | |||
| (ac-js2-format-node-doc node)))) | |||
| `(,name-format . ,doc))) | |||
| (defun ac-js2-format-object-node-doc (obj-node) | |||
| "Format OBJ-NODE to display as documentation." | |||
| (let (elems) | |||
| (unless (js2-object-node-p obj-node) | |||
| (error "Node is not an object node")) | |||
| (setq elems (js2-object-node-elems obj-node)) | |||
| (if (not elems) | |||
| "{}" | |||
| (mapconcat #'(lambda (x) (ac-js2-format-js2-object-prop-doc x)) elems "\n")))) | |||
| (defun ac-js2-format-node-doc (node) | |||
| "Format NODE for displaying in a document string." | |||
| (let* ((node-above (and node (js2-node-at-point | |||
| (save-excursion | |||
| (goto-char (js2-node-abs-pos node)) | |||
| (forward-line -1) | |||
| (point))))) | |||
| (comment (if (js2-comment-node-p node-above) | |||
| (ac-js2-format-comment (js2-node-string node-above)))) | |||
| (doc (cond | |||
| ((js2-function-node-p node) | |||
| (ac-js2-format-function node)) | |||
| ((js2-object-node-p node) | |||
| (ac-js2-format-object-node-doc node)) | |||
| ((js2-object-prop-node-p node) | |||
| (ac-js2-format-node-doc (js2-object-prop-node-right node))) | |||
| (t | |||
| (if (js2-node-p node) (js2-node-string node) ""))))) | |||
| (if comment (concat comment "\n" doc) doc))) | |||
| (defun ac-js2-format-js2-object-prop-doc (obj-prop) | |||
| "Format an OBJ-PROP for displaying as a document string." | |||
| (unless (js2-object-prop-node-p obj-prop) | |||
| (error "Node is not an object property node")) | |||
| (let* ((left (js2-object-prop-node-left obj-prop)) | |||
| (right (js2-object-prop-node-right obj-prop))) | |||
| (concat (js2-node-string left) " : " | |||
| (ac-js2-format-node-doc right)))) | |||
| (defun ac-js2-format-function (func) | |||
| "Formats a function for a document string. | |||
| FUNC can be either a function node or a string starting with | |||
| 'function'. Returns nil if neither." | |||
| (let ((str (or (and (js2-function-node-p func) (js2-node-string func)) | |||
| (and (stringp func) (eq 0 (string-match "function" func)) func)))) | |||
| (if str (substring str 0 (1+ (string-match ")" str)))))) | |||
| (defun ac-js2-format-comment (comment) | |||
| "Prepare a COMMENT node for displaying in a popup." | |||
| (let* ((node-string (if (js2-comment-node-p comment) | |||
| (js2-node-string comment) | |||
| comment)) | |||
| (string (replace-regexp-in-string "[ \t]$" "" | |||
| (replace-regexp-in-string "^[ \t\n*/*]+" "" node-string)))) | |||
| string)) | |||
| ;;; Navigation commands for js2-mode | |||
| (defun ac-js2-find-property (list-names) | |||
| "Find the property definition that consists of LIST-NAMES. | |||
| Supports navigation to 'foo.bar = 3' and 'foo = {bar: 3}'." | |||
| (catch 'prop-found | |||
| (js2-visit-ast-root | |||
| js2-mode-ast | |||
| (lambda (node endp) | |||
| (let ((parent (js2-node-parent node))) | |||
| (unless endp | |||
| (if (or (and (js2-prop-get-node-p node) | |||
| (not (or (js2-elem-get-node-p parent) (js2-call-node-p parent))) | |||
| (equal list-names (ac-js2-build-prop-name-list node))) | |||
| (and (js2-name-node-p node) | |||
| (js2-object-prop-node-p parent) | |||
| (string= (js2-name-node-name node) | |||
| (first list-names)))) | |||
| (throw 'prop-found node)) | |||
| t)))))) | |||
| (defun ac-js2-get-function-node (name scope) | |||
| "Return node of function named NAME in SCOPE." | |||
| (catch 'function-found | |||
| (js2-visit-ast | |||
| scope | |||
| (lambda (node end-p) | |||
| (when (and (not end-p) | |||
| (string= name (ac-js2-get-function-name node))) | |||
| (throw 'function-found node)) | |||
| t)) | |||
| nil)) | |||
| ;;;###autoload | |||
| (defun ac-js2-jump-to-definition () | |||
| "Jump to the definition of an object's property, variable or function. | |||
| Navigation to a property definend in an Object literal isn't | |||
| implemented." | |||
| (interactive) | |||
| (ring-insert find-tag-marker-ring (point-marker)) | |||
| (let* ((node (js2-node-at-point)) | |||
| (parent (js2-node-parent node)) | |||
| (prop-names (if (js2-prop-get-node-p parent) | |||
| (ac-js2-prop-names-left node))) | |||
| (name (if (and (js2-name-node-p node) | |||
| (not (js2-object-prop-node-p parent))) | |||
| (js2-name-node-name node) | |||
| (error "Node is not a supported jump node"))) | |||
| (node-init (if (and prop-names (listp prop-names)) | |||
| (ac-js2-find-property prop-names) | |||
| (ac-js2-name-declaration name)))) | |||
| (unless node-init | |||
| (pop-tag-mark) | |||
| (error "No jump location found")) | |||
| (goto-char (js2-node-abs-pos node-init)))) | |||
| (defun ac-js2-get-function-name (fn-node) | |||
| "Return the name of the function FN-NODE. | |||
| Value may be either function name or the variable name that holds | |||
| the function." | |||
| (let ((parent (js2-node-parent fn-node))) | |||
| (if (js2-function-node-p fn-node) | |||
| (or (js2-function-name fn-node) | |||
| (if (js2-var-init-node-p parent) | |||
| (js2-name-node-name (js2-var-init-node-target parent))))))) | |||
| (defvar ac-js2-mode-map | |||
| (let ((map (make-sparse-keymap))) | |||
| (define-key map (kbd "M-.") 'ac-js2-jump-to-definition) | |||
| (define-key map (kbd "M-,") 'pop-tag-mark) | |||
| (define-key map (kbd "C-c C-c") 'ac-js2-expand-function) | |||
| map) | |||
| "Keymap for `ac-js2-mode'.") | |||
| ;;; Minor mode | |||
| ;;;###autoload | |||
| (define-minor-mode ac-js2-mode | |||
| "A minor mode that provides auto-completion and navigation for Js2-mode." | |||
| :keymap ac-js2-mode-map | |||
| (if (featurep 'auto-complete) | |||
| (ac-js2-setup-auto-complete-mode)) | |||
| (set (make-local-variable 'completion-at-point-functions) | |||
| (cons 'ac-js2-completion-function completion-at-point-functions)) | |||
| (ac-js2-skewer-eval-wrapper (buffer-string)) | |||
| (add-hook 'before-save-hook 'ac-js2-save nil t) | |||
| (add-hook 'skewer-js-hook 'ac-js2-on-skewer-load)) | |||
| (provide 'ac-js2) | |||
| ;;; ac-js2.el ends here | |||
| @ -0,0 +1,116 @@ | |||
| /** | |||
| * @fileOverview Completion request handler for skewer.js | |||
| * @requires skewer | |||
| * @version 1.0 | |||
| */ | |||
| /** | |||
| * Handles a completion request from Emacs. | |||
| * @param request The request object sent by Emacs | |||
| * @returns The completions and init values to be returned to Emacs | |||
| */ | |||
| skewer.fn.complete = function(request) { | |||
| var result = { | |||
| type : request.type, | |||
| id : request.id, | |||
| strict : request.strict, | |||
| status : "success" | |||
| }, | |||
| /** | |||
| * Methods for generating candidates | |||
| */ | |||
| METHOD = { | |||
| EVAL : 0, | |||
| GLOBAL : 1 | |||
| }, | |||
| /** | |||
| * Add the properties from object to extendObject. Properties | |||
| * may be from the prototype but we still want to add them. | |||
| */ | |||
| extend = function(extendObject, object) { | |||
| for(var key in object) { | |||
| extendObject[key] = object[key]; | |||
| } | |||
| }, | |||
| globalCompletion = function() { | |||
| var global = Function('return this')(), | |||
| keys = Object.keys(global); | |||
| candidates = buildCandidates(global, keys); | |||
| }, | |||
| evalCompletion = function(evalObject) { | |||
| var obj = (eval, eval)(evalObject); | |||
| if (typeof obj === "object") { | |||
| candidates = buildCandidates(obj) || {}; | |||
| while (request.prototypes && (obj = Object.getPrototypeOf(obj)) !== null) { | |||
| extend(candidates, buildCandidates(obj)); | |||
| } | |||
| } else if (typeof obj === "function"){ | |||
| candidates = buildCandidates(obj) || {}; | |||
| extend(candidates, buildCandidates(Object.getPrototypeOf(obj))); | |||
| if (request.prototypes) { | |||
| var protoObject = Object.getPrototypeOf(obj.prototype); | |||
| if (protoObject !== null) { | |||
| extend(candidates, buildCandidates(protoObject)); | |||
| } else { | |||
| extend(candidates, buildCandidates(obj.prototype)); | |||
| } | |||
| } | |||
| } | |||
| }, | |||
| /** | |||
| * Completion candidates sent back to Emacs. Keys are | |||
| * completion candidates the values are the inital items or | |||
| * function interfaces. | |||
| */ | |||
| candidates = {}, | |||
| /** | |||
| * Build the candiates to return to Emacs. | |||
| * @param obj The object to get candidates from | |||
| * @param items The selected keys from obj to create candidates for | |||
| * @return object containing completion candidates and documentation strings | |||
| */ | |||
| buildCandidates = function(obj, items) { | |||
| var keys = items || Object.getOwnPropertyNames(obj), values = {}; | |||
| for (var i = 0; i < keys.length; i++) { | |||
| var key = keys[i]; | |||
| if (key === "callee" || key === "caller" || key === "arguments") continue; | |||
| if (Object.prototype.toString.call(obj[key]) === "[object Function]") { | |||
| values[key] = obj[key].toString(); | |||
| } else if (typeof obj[key] === "object"){ | |||
| values[key] = "[object Object]"; | |||
| } else if (typeof obj[key] === "number") { | |||
| if (!(obj instanceof Array)) { | |||
| values[key] = obj[key].toString(); | |||
| } | |||
| } else if (typeof obj[key] === "string") { | |||
| values[key] = obj[key].toString(); | |||
| } else if(obj[key] === true) { | |||
| values[key] = "true"; | |||
| } else if (obj[key] === false) { | |||
| values[key] = "false"; | |||
| } else { | |||
| values[key] = ""; | |||
| } | |||
| } | |||
| return values; | |||
| }; | |||
| try { | |||
| switch (request.method) { | |||
| case METHOD.GLOBAL: | |||
| globalCompletion(); | |||
| break; | |||
| default: | |||
| evalCompletion(request.eval); | |||
| } | |||
| result.value = candidates; | |||
| } catch (error){ | |||
| skewer.errorResult(error, result, request); | |||
| } | |||
| return result; | |||
| }; | |||
| @ -0,0 +1,59 @@ | |||
| ;;; alchemist-autoloads.el --- automatically extracted autoloads | |||
| ;; | |||
| ;;; Code: | |||
| (add-to-list 'load-path (or (file-name-directory #$) (car load-path))) | |||
| ;;;### (autoloads nil "alchemist" "alchemist.el" (21721 25170 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" (21721 25170 | |||
| ;;;;;; 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 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-utils.el") | |||
| ;;;;;; (21721 25170 129388 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-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--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)) | |||
| (setq-local compilation-error-regexp-alist-alist | |||
| (cons alchemist-buffer--error-link-options compilation-error-regexp-alist-alist)) | |||
| (setq-local 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--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 | |||
| @ -0,0 +1,87 @@ | |||
| ;;; 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 () | |||
| (interactive) | |||
| (company--electric-do | |||
| (let* ((selected (nth company-selection company-candidates)) | |||
| (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 () | |||
| (interactive) | |||
| (company--electric-do | |||
| (let* ((selected (nth company-selection company-candidates))) | |||
| (alchemist-goto--open-definition selected)))) | |||
| (put 'alchemist-company--open-definition 'company-keep t) | |||
| (defun alchemist-company--keybindings () | |||
| (define-key company-active-map (kbd "C-d") 'alchemist-company--show-documentation) | |||
| (define-key company-active-map (kbd "M-.") 'alchemist-company--open-definition)) | |||
| (add-hook 'company-mode-hook 'alchemist-company--keybindings) | |||
| (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))) | |||
| (candidates (cons :async | |||
| (lambda (cb) (alchemist-complete-candidates arg cb)))) | |||
| (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,73 @@ | |||
| ;;; 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) | |||
| (when (not (file-exists-p filename)) | |||
| (error "The given file doesn't exists")) | |||
| (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 | |||
| @ -0,0 +1,186 @@ | |||
| ;;; 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) | |||
| (defvar alchemist-complete-debug-mode t) | |||
| (defun alchemist-complete-debug-mode () | |||
| "Enables the debug mode for completion if `alchemist-complete-debug-mode' | |||
| is `nil', otherwise it disable it." | |||
| (interactive) | |||
| (setq alchemist-complete-debug-mode (not alchemist-complete-debug-mode)) | |||
| (let ((state (if alchemist-complete-debug-mode | |||
| "ENABLED" | |||
| "DISABLED"))) | |||
| (message "Alchemist complete debug mode is: %s" state))) | |||
| (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 (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))))) | |||
| (cdr a-list)))) | |||
| 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--elixir-complete-code (exp) | |||
| (format " | |||
| defmodule Alchemist do | |||
| def expand(exp) do | |||
| {status, result, list } = IEx.Autocomplete.expand(Enum.reverse(exp)) | |||
| case { status, result, list } do | |||
| { :no, _, _ } -> '' | |||
| { :yes, [], _ } -> List.insert_at(list, 0, exp) | |||
| { :yes, _, _ } -> expand(exp ++ result) | |||
| end | |||
| end | |||
| end | |||
| Alchemist.expand('%s') |> Enum.map fn (f) -> IO.puts('cmp:' ++ f) end | |||
| " exp)) | |||
| (defun alchemist-complete--command (exp) | |||
| (let* ((elixir-code (alchemist-complete--elixir-complete-code exp)) | |||
| (compile-option (if (and (alchemist-project-p) | |||
| (alchemist-project--load-compile-when-needed-setting)) | |||
| "" | |||
| "--no-compile")) | |||
| (command (if (alchemist-project-p) | |||
| (format "%s %s -e \"%s\"" alchemist-help-mix-run-command compile-option elixir-code) | |||
| (format "%s -e \"%s\"" alchemist-execute-command elixir-code))) | |||
| ) | |||
| (when (alchemist-project-p) | |||
| (alchemist-project--establish-root-directory)) | |||
| command)) | |||
| (defun alchemist-complete--sentinel (proc callback &optional format-function) | |||
| (set-process-sentinel proc (lambda (process signal) | |||
| (cond ((equal signal "finished\n") | |||
| (alchemist-complete--clear-buffer (process-buffer process)) | |||
| (let* ((candidates (alchemist-complete--output-to-list | |||
| (alchemist--utils-clear-ansi-sequences | |||
| (alchemist-utils--get-buffer-content (process-buffer process))))) | |||
| (candidates (if format-function | |||
| (funcall format-function candidates) | |||
| candidates))) | |||
| (funcall callback candidates))) | |||
| (t | |||
| (when alchemist-complete-debug-mode | |||
| (alchemist-complete--debug-message (alchemist-utils--get-buffer-content (process-buffer process)))) | |||
| (funcall callback '()))) | |||
| (alchemist-utils--erase-buffer (process-buffer process))))) | |||
| (defun alchemist-complete--debug-message (content) | |||
| (alchemist-message (format "== ALCHEMIST COMPLETION FAILED ==\n== OUTPUT BEGIN:\n%s== OUTPUT END:" | |||
| content))) | |||
| (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 alchemist-complete (exp callback) | |||
| (let* ((buffer (get-buffer-create "alchemist-complete-buffer")) | |||
| (command (alchemist-complete--command exp)) | |||
| (proc (start-process-shell-command "alchemist-complete-proc" buffer command))) | |||
| (alchemist-complete--sentinel proc callback))) | |||
| (defun alchemist-complete-candidates (exp callback) | |||
| (let* ((buffer (get-buffer-create "alchemist-complete-buffer")) | |||
| (command (alchemist-complete--command exp)) | |||
| (proc (start-process-shell-command "alchemist-complete-proc" buffer command))) | |||
| (alchemist-complete--sentinel proc callback #'alchemist-complete--build-candidates))) | |||
| (provide 'alchemist-complete) | |||
| ;;; alchemist-complete.el ends here | |||
| @ -0,0 +1,175 @@ | |||
| ;;; 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) | |||
| (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))) | |||
| 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--build-code-evaluation-command (file) | |||
| (format "%s -e 'IO.inspect(elem(Code.eval_string(File.read!(\"%s\")), 0))'" | |||
| (alchemist-eval--runner) | |||
| file)) | |||
| (defun alchemist-eval--build-code-evaluation-as-quoted-command (file) | |||
| (format "%s -e 'IO.puts inspect(elem(Code.string_to_quoted(File.read!(\"%s\")), 1), pretty: true)'" | |||
| (alchemist-eval--runner) | |||
| file)) | |||
| (defun alchemist-eval--runner () | |||
| (if (alchemist-project-p) | |||
| (format "%s run --no-compile" alchemist-mix-command) | |||
| alchemist-execute-command)) | |||
| (defun alchemist-eval-current-line () | |||
| "Evaluate the Elixir code on the current line." | |||
| (interactive) | |||
| (let ((current-line (thing-at-point 'line))) | |||
| (message (alchemist-eval--evaluate-code current-line)))) | |||
| (defun alchemist-eval-print-current-line () | |||
| "Evaluate the Elixir code on the current line and insert the result." | |||
| (interactive) | |||
| (let ((current-line (thing-at-point 'line))) | |||
| (alchemist-eval--insert (alchemist-eval--evaluate-code current-line)))) | |||
| (defun alchemist-eval-quoted-current-line () | |||
| "Get the Elixir code representation of the expression on the current line." | |||
| (interactive) | |||
| (let ((current-line (thing-at-point 'line))) | |||
| (message (alchemist-eval--evaluate-code-as-quoted current-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) | |||
| (let ((current-line (thing-at-point 'line))) | |||
| (alchemist-eval--insert (alchemist-eval--evaluate-code-as-quoted current-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))) | |||
| (message (alchemist-eval--evaluate-code 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--insert (alchemist-eval--evaluate-code string)))) | |||
| (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))) | |||
| (message (alchemist-eval--evaluate-code-as-quoted 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--insert (alchemist-eval--evaluate-code-as-quoted string)))) | |||
| (defun alchemist-eval-buffer () | |||
| "Evaluate the Elixir code in the current buffer." | |||
| (interactive) | |||
| (let ((string (buffer-substring-no-properties (point-min) (point-max)))) | |||
| (message (alchemist-eval--evaluate-code 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--insert (alchemist-eval--evaluate-code 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)))) | |||
| (message (alchemist-eval--evaluate-code-as-quoted 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--insert (alchemist-eval--evaluate-code-as-quoted string)))) | |||
| (provide 'alchemist-eval) | |||
| ;;; alchemist-eval.el ends here | |||
| @ -0,0 +1,73 @@ | |||
| ;;; 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 | |||
| @ -0,0 +1,231 @@ | |||
| ;;; alchemist-goto.el --- Functionality to jump modules and function definitions | |||
| ;; Copyright © 2015 Samuel Tonini | |||
| ;; Author: Samuel Tonini <tonini.samuel@gmail.com | |||
| ;; This file is not part of GNU Emacs. | |||
| ;; This program is free software: you can redistribute it and/or modify | |||
| ;; it under the terms of the GNU General Public License as published by | |||
| ;; the Free Software Foundation, either version 3 of the License, or | |||
| ;; (at your option) any later version. | |||
| ;; This program is distributed in the hope that it will be useful, | |||
| ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| ;; GNU General Public License for more details. | |||
| ;; You should have received a copy of the GNU General Public License | |||
| ;; along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| ;;; Commentary: | |||
| ;; Functionality to jump modules and function definitions | |||
| ;;; Code: | |||
| (defgroup alchemist-goto nil | |||
| "Functionality to jump modules and function definitions." | |||
| :prefix "alchemist-goto-" | |||
| :group 'alchemist) | |||
| (defcustom alchemist-goto-erlang-source-dir "" | |||
| "Path to the erlang source code." | |||
| :type 'string | |||
| :group 'alchemist-goto) | |||
| (defcustom alchemist-goto-elixir-source-dir "" | |||
| "Path to the elixir source code." | |||
| :type 'string | |||
| :group 'alchemist-goto) | |||
| (defun alchemist-goto--extract-module (code) | |||
| "Extract module from CODE." | |||
| (let* ((parts (split-string code "\\.")) | |||
| (function (car (last parts))) | |||
| (case-fold-search nil)) | |||
| (when (string-match-p "^[a-z]+" function) | |||
| (delete function parts)) | |||
| (unless (string-match-p "^[a-z]+" (car parts)) | |||
| (mapconcat 'concat parts ".")))) | |||
| (defun alchemist-goto--extract-function (code) | |||
| "Extract function from CODE." | |||
| (let* ((parts (split-string code "\\.")) | |||
| (function (car (last parts))) | |||
| (case-fold-search nil)) | |||
| (when (and function | |||
| (string-match-p "^[a-z]+" function)) | |||
| function))) | |||
| (defun alchemist-goto--build-elixir-ex-core-file (file) | |||
| (when (string-match "\\/\\(lib\\/.+\\/lib\\)\\/.+\.ex$" file) | |||
| (let* ((file (substring-no-properties file (match-beginning 1))) | |||
| (source-directory (expand-file-name alchemist-goto-elixir-source-dir))) | |||
| (concat source-directory file)))) | |||
| (defun alchemist-goto--build-elixir-erl-core-file (file) | |||
| (when (string-match "\\/\\(lib\\/.+\\/src\\)\\/.+\.erl$" file) | |||
| (let* ((file (substring-no-properties file (match-beginning 1))) | |||
| (source-directory (expand-file-name alchemist-goto-elixir-source-dir))) | |||
| (concat source-directory file)))) | |||
| (defun alchemist-goto--build-erlang-core-file (file) | |||
| (when (string-match "\\/\\(lib\\/.+\\/src\\)\\/.+\.erl$" file) | |||
| (let* ((file (substring-no-properties file (match-beginning 1))) | |||
| (source-directory (expand-file-name alchemist-goto-erlang-source-dir))) | |||
| (concat source-directory file)))) | |||
| (defun alchemist-goto--elixir-file-p (file) | |||
| (string-match-p "\\.ex\\(s\\)?$" file)) | |||
| (defun alchemist-goto--erlang-file-p (file) | |||
| (string-match-p "\\.erl$" file)) | |||
| (defun alchemist-goto--get-full-path-of-alias (module) | |||
| (let* ((aliases (mapcar (lambda (m) | |||
| (when (string= module (car (cdr m))) | |||
| (car m))) (alchemist-goto--alises-of-current-buffer))) | |||
| (aliases (delete nil aliases))) | |||
| (if aliases | |||
| (car aliases) | |||
| module))) | |||
| (defun alchemist-goto--open-definition (expr) | |||
| (let* ((module (alchemist-goto--extract-module expr)) | |||
| (module (alchemist-goto--get-full-path-of-alias module)) | |||
| (module (if module module "AlchemistGoto")) | |||
| (function (alchemist-goto--extract-function expr)) | |||
| (function (if function function "\"\"")) | |||
| (file (alchemist-goto--get-module-source module function))) | |||
| (ring-insert find-tag-marker-ring (point-marker)) | |||
| (cond ((equal file nil) | |||
| (message "Don't know how to find: %s" expr)) | |||
| ((file-exists-p file) | |||
| (alchemist-goto--open-file file module function)) | |||
| ((alchemist-goto--elixir-file-p file) | |||
| (let* ((elixir-source-file (alchemist-goto--build-elixir-ex-core-file file))) | |||
| (if (file-exists-p elixir-source-file) | |||
| (alchemist-goto--open-file elixir-source-file module function) | |||
| (message "Don't know how to find: %s" expr)))) | |||
| ((alchemist-goto--erlang-file-p file) | |||
| (let* ((elixir-source-file (alchemist-goto--build-elixir-erl-core-file file)) | |||
| (erlang-source-file (alchemist-goto--build-erlang-core-file file))) | |||
| (cond ((file-exists-p elixir-source-file) | |||
| (alchemist-goto--open-file elixir-source-file module function)) | |||
| ((file-exists-p erlang-source-file) | |||
| (alchemist-goto--open-file erlang-source-file module function)) | |||
| (t | |||
| (message "Don't know how to find: %s" expr))))) | |||
| (t | |||
| (pop-tag-mark) | |||
| (message "Don't know how to find: %s" expr))))) | |||
| (defun alchemist-goto--open-file (file module function) | |||
| (let* ((buf (find-file-noselect file))) | |||
| (switch-to-buffer buf) | |||
| (beginning-of-buffer) | |||
| (cond ((alchemist-goto--elixir-file-p file) | |||
| (alchemist-goto--jump-to-elixir-source module function)) | |||
| ((alchemist-goto--erlang-file-p file) | |||
| (alchemist-goto--jump-to-erlang-source module function))))) | |||
| (defun alchemist-goto--jump-to-elixir-source (module function) | |||
| (let ((function (replace-regexp-in-string "\?" "\\?" function))) | |||
| (when (re-search-forward (format "^\s+\\(defp?\s+%s\(\\|defmacrop?\s+%s\(\\)" function function function) nil t) | |||
| (goto-char (match-beginning 0))) | |||
| (when (re-search-forward (format "\\(defmodule\\|defimpl\\|defprotocol\\)\s+%s\s+do" module) nil t) | |||
| (goto-char (match-beginning 0))))) | |||
| (defun alchemist-goto--jump-to-erlang-source (module function) | |||
| (when (re-search-forward (format "\\(^%s\(\\)" function) nil t) | |||
| (goto-char (match-beginning 0))) | |||
| (when (re-search-forward (format "\\(^-module\(%s\)\\)" (substring module 1)) nil t) | |||
| (goto-char (match-beginning 0)))) | |||
| (defun alchemist-goto--clear-output (output) | |||
| (let* ((output (replace-regexp-in-string "source-file-path:" "" output)) | |||
| (output (replace-regexp-in-string "\n" "" output)) | |||
| (output (alchemist--utils-clear-ansi-sequences output)) | |||
| (output (if (string= output "") nil output))) | |||
| output)) | |||
| (defun alchemist-goto--debug-message (output) | |||
| (alchemist-message (format "== ALCHEMIST GOTO FAILED ==\n== OUTPUT BEGIN:\n%s== OUTPUT END:" | |||
| output))) | |||
| (defun alchemist-goto--report-errors (output) | |||
| (when (and (not (string-match-p "source-file-path:" output)) | |||
| (not (string= (alchemist--utils-clear-ansi-sequences | |||
| (replace-regexp-in-string "\n" "" output)) ""))) | |||
| (when alchemist-complete-debug-mode | |||
| (alchemist-goto--debug-message output)))) | |||
| (defun alchemist-goto--runner () | |||
| (if (alchemist-project-p) | |||
| (format "%s run --no-compile" alchemist-mix-command) | |||
| alchemist-execute-command)) | |||
| (defun alchemist-goto--get-module-source (module function) | |||
| (let* ((default-directory (if (alchemist-project-p) | |||
| (alchemist-project-root) | |||
| default-directory)) | |||
| (source-file (shell-command-to-string (format "%s -e '%s'" | |||
| (alchemist-goto--runner) | |||
| (alchemist-goto--get-module-source-code module function))))) | |||
| (alchemist-goto--report-errors source-file) | |||
| (alchemist-goto--clear-output source-file))) | |||
| (defun alchemist-goto--get-module-source-code (module function) | |||
| (format " | |||
| defmodule Source do | |||
| def find(module, function) do | |||
| cond do | |||
| Code.ensure_loaded?(module) -> | |||
| IO.puts source(module) | |||
| List.keymember?(Kernel.module_info[:exports], function, 0) -> | |||
| IO.puts source(Kernel) | |||
| true -> | |||
| IO.puts \"\" | |||
| end | |||
| end | |||
| defp source(module) do | |||
| source = module.module_info(:compile)[:source] | |||
| case source do | |||
| nil -> nil | |||
| source -> \"source-file-path:\" <> List.to_string(source) | |||
| end | |||
| end | |||
| end | |||
| Source.find(%s, :%s)" module function)) | |||
| (defun alchemist-goto--alises-of-current-buffer () | |||
| (let* ((aliases '())) | |||
| (save-excursion | |||
| (goto-char (point-min)) | |||
| (while (re-search-forward "^\s+alias\s+\\([-_A-Za-z0-9,\.\?!\]+\\)\\(\s*,\s*as:\s*\\)?\\([-_A-Za-z0-9,\.\?!\]+\\)?\n" nil t) | |||
| (let* ((alias (match-string 1)) | |||
| (as (if (match-string 3) (match-string 3) nil)) | |||
| (as (if as as (car (last (split-string alias "\\.")))))) | |||
| (setq aliases (append aliases (list (list alias as))))))) | |||
| aliases)) | |||
| (defun alchemist-goto-definition-at-point () | |||
| "Jump to the elixir expression definition at point." | |||
| (interactive) | |||
| (let (p1 p2) | |||
| (skip-chars-backward "-_A-Za-z0-9.?!:") | |||
| (setq p1 (point)) | |||
| (skip-chars-forward "-_A-Za-z0-9.?!:") | |||
| (setq p2 (point)) | |||
| (alchemist-goto--open-definition (buffer-substring-no-properties p1 p2)))) | |||
| (defalias 'alchemist-goto-jump-back 'pop-tag-mark) | |||
| (provide 'alchemist-goto) | |||
| ;;; alchemist-goto.el ends here | |||
| @ -0,0 +1,264 @@ | |||
| ;;; alchemist-help.el --- Functionality for Elixir documentation lookup -*- lexical-binding: t -*- | |||
| ;; Copyright © 2014-2015 Samuel Tonini | |||
| ;; Author: Samuel Tonini <tonini.samuel@gmail.com | |||
| ;; This file is not part of GNU Emacs. | |||
| ;; This program is free software: you can redistribute it and/or modify | |||
| ;; it under the terms of the GNU General Public License as published by | |||
| ;; the Free Software Foundation, either version 3 of the License, or | |||
| ;; (at your option) any later version. | |||
| ;; This program is distributed in the hope that it will be useful, | |||
| ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| ;; GNU General Public License for more details. | |||
| ;; You should have received a copy of the GNU General Public License | |||
| ;; along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| ;;; Commentary: | |||
| ;; Functionality for Elixir documentation lookup. | |||
| ;;; Code: | |||
| (defgroup alchemist-help nil | |||
| "Functionality for Elixir documentation lookup." | |||
| :prefix "alchemist-help-" | |||
| :group 'alchemist) | |||
| ;; Variables | |||
| (defcustom alchemist-help-ansi-color-docs t | |||
| "If t, `alchemist-help' will present ansi colored documentation." | |||
| :type 'boolean | |||
| :group 'alchemist-help) | |||
| (defcustom alchemist-help-buffer-name "*elixir help*" | |||
| "Name of the Elixir help buffer." | |||
| :type 'string | |||
| :group 'alchemist-help) | |||
| (defvar alchemist-help-mix-run-command "mix run" | |||
| "The shell command for 'mix run'.") | |||
| (defvar alchemist-help-search-history '() | |||
| "Storage for the search history.") | |||
| (defvar alchemist-help-current-search-text '() | |||
| "Stores the current search.") | |||
| ;; Faces | |||
| (defface alchemist-help--key-face | |||
| '((t (:inherit font-lock-variable-name-face :bold t :foreground "red"))) | |||
| "Fontface for the letter keys in the summary." | |||
| :group 'alchemist-help) | |||
| (defun alchemist-help--load-ansi-color-setting () | |||
| (let ((config (gethash "ansi-color-docs" (alchemist-project-config)))) | |||
| (if config | |||
| (intern config) | |||
| alchemist-help-ansi-color-docs))) | |||
| (defun alchemist-help--exp-at-point () | |||
| "Return the expression under the cursor." | |||
| (let (p1 p2) | |||
| (save-excursion | |||
| (skip-chars-backward "-_A-Za-z0-9.?!:") | |||
| (setq p1 (point)) | |||
| (skip-chars-forward "-_A-Za-z0-9.?!:") | |||
| (setq p2 (point)) | |||
| (buffer-substring-no-properties p1 p2)))) | |||
| (defun alchemist-help--start-help-process (exp callback) | |||
| (let* ((buffer (get-buffer-create "alchemist-help-buffer")) | |||
| (command (alchemist-help--eval-string-command (alchemist-help--build-code-for-search exp))) | |||
| (proc (start-process-shell-command "alchemist-help-proc" buffer command))) | |||
| (set-process-sentinel proc (lambda (process signal) | |||
| (when (equal signal "finished\n") | |||
| (funcall callback (alchemist-utils--get-buffer-content (process-buffer process)))) | |||
| (alchemist-utils--erase-buffer (process-buffer process)))))) | |||
| (defun alchemist-help--execute (search) | |||
| (let ((last-directory default-directory) | |||
| (last-buffer (current-buffer))) | |||
| (alchemist-complete search (lambda (candidates) | |||
| (if candidates | |||
| (let* ((search (alchemist-complete--completing-prompt search candidates))) | |||
| (setq alchemist-help-current-search-text search) | |||
| (alchemist-help--start-help-process search (lambda (output) | |||
| (alchemist-help--initialize-buffer output) | |||
| (with-current-buffer last-buffer | |||
| (cd last-directory))))) | |||
| (message "No documentation found for '%s'" search)))))) | |||
| (defun alchemist-help--execute-without-complete (search) | |||
| (setq alchemist-help-current-search-text search) | |||
| (let ((last-directory default-directory) | |||
| (last-buffer (current-buffer))) | |||
| (alchemist-help--start-help-process search (lambda (output) | |||
| (alchemist-help--initialize-buffer output) | |||
| (with-current-buffer last-buffer | |||
| (cd last-directory)))))) | |||
| (defun alchemist-help--build-code-for-search (string) | |||
| (format "import IEx.Helpers | |||
| Application.put_env(:iex, :colors, [enabled: %s]) | |||
| h(%s)" (if (alchemist-help--load-ansi-color-setting) "true" "false") string)) | |||
| (defun alchemist-help--eval-string-command (string) | |||
| (when (alchemist-project-p) | |||
| (alchemist-project--establish-root-directory)) | |||
| (let* ((compile-option (if (and (alchemist-project-p) | |||
| (alchemist-project--load-compile-when-needed-setting)) | |||
| "" | |||
| "--no-compile")) | |||
| (command (if (alchemist-project-p) | |||
| (format "%s %s -e \"%s\"" alchemist-help-mix-run-command compile-option string) | |||
| (format "%s -e \"%s\"" alchemist-execute-command string)))) | |||
| command)) | |||
| (defun alchemist-help--bad-search-output-p (string) | |||
| (let ((match (or (string-match-p "No documentation for " string) | |||
| (string-match-p "Invalid arguments for h helper" string) | |||
| (string-match-p "** (TokenMissingError)" string) | |||
| (string-match-p "** (SyntaxError)" string) | |||
| (string-match-p "** (FunctionClauseError)" string) | |||
| (string-match-p "** (CompileError)" string) | |||
| (string-match-p "Could not load module" string)))) | |||
| (if match | |||
| t | |||
| nil))) | |||
| (defun alchemist-help--initialize-buffer (content) | |||
| (pop-to-buffer alchemist-help-buffer-name) | |||
| (setq buffer-undo-list nil) | |||
| (let ((inhibit-read-only t) | |||
| (buffer-undo-list t)) | |||
| (cond ((alchemist-help--bad-search-output-p content) | |||
| (message (propertize | |||
| (format "No documentation for [ %s ] found." alchemist-help-current-search-text) | |||
| 'face 'alchemist-help--key-face))) | |||
| (t | |||
| (erase-buffer) | |||
| (insert content) | |||
| (unless (memq 'alchemist-help-current-search-text alchemist-help-search-history) | |||
| (add-to-list 'alchemist-help-search-history alchemist-help-current-search-text)))) | |||
| (delete-matching-lines "do not show this result in output" (point-min) (point-max)) | |||
| (delete-matching-lines "^Compiled lib\\/" (point-min) (point-max)) | |||
| (ansi-color-apply-on-region (point-min) (point-max)) | |||
| (toggle-read-only 1) | |||
| (alchemist-help-minor-mode 1))) | |||
| (defun alchemist-help-minor-mode-key-binding-summary () | |||
| (interactive) | |||
| (message | |||
| (concat "[" (propertize "q" 'face 'alchemist-help--key-face) | |||
| "]-quit [" | |||
| (propertize "e" 'face 'alchemist-help--key-face) | |||
| "]-search-at-point [" | |||
| (propertize "m" 'face 'alchemist-help--key-face) | |||
| "]-search-marked-region [" | |||
| (propertize "s" 'face 'alchemist-help--key-face) | |||
| "]-search [" | |||
| (propertize "h" 'face 'alchemist-help--key-face) | |||
| "]-history [" | |||
| (propertize "?" 'face 'alchemist-help--key-face) | |||
| "]-keys"))) | |||
| (defun alchemist-help-search-at-point () | |||
| "Search through `alchemist-help' with the expression under the cursor." | |||
| (interactive) | |||
| (alchemist-help--execute (alchemist-help--exp-at-point))) | |||
| (defun alchemist-help-search-marked-region (begin end) | |||
| "Run `alchemist-help' with the marked region. | |||
| Argument BEGIN where the mark starts. | |||
| Argument END where the mark ends." | |||
| (interactive "r") | |||
| (let ((region (filter-buffer-substring begin end))) | |||
| (alchemist-help--execute region))) | |||
| (defun alchemist-help--elixir-modules-to-list (str) | |||
| (let* ((modules (split-string str)) | |||
| (modules (mapcar (lambda (m) | |||
| (when (string-match-p "Elixir\\." m) | |||
| (replace-regexp-in-string "Elixir\\." "" m))) modules)) | |||
| (modules (delete nil modules)) | |||
| (modules (cl-sort modules 'string-lessp :key 'downcase)) | |||
| (modules (delete-dups modules))) | |||
| modules) | |||
| ) | |||
| (defun alchemist-help--get-modules () | |||
| (let* ((elixir-code " | |||
| defmodule AlchemistModule do | |||
| def get_modules do | |||
| modules = Enum.map(:code.all_loaded, fn({m, _}) -> Atom.to_string(m) end) | |||
| if :code.get_mode() === :interactive do | |||
| modules ++ get_modules_from_applications() | |||
| else | |||
| modules | |||
| end | |||
| end | |||
| defp get_modules_from_applications do | |||
| for {app, _, _} <- :application.loaded_applications, | |||
| {_, modules} = :application.get_key(app, :modules), | |||
| module <- modules, | |||
| has_doc = Code.get_docs(module, :moduledoc), elem(has_doc, 1) do | |||
| Atom.to_string(module) | |||
| end | |||
| end | |||
| end | |||
| AlchemistModule.get_modules |> Enum.map &IO.puts/1 | |||
| ") | |||
| (command (if (alchemist-project-p) | |||
| (format "%s -e \"%s\"" alchemist-help-mix-run-command elixir-code) | |||
| (format "%s -e \"%s\"" alchemist-execute-command elixir-code)))) | |||
| (when (alchemist-project-p) | |||
| (alchemist-project--establish-root-directory)) | |||
| (alchemist-help--elixir-modules-to-list (shell-command-to-string command)))) | |||
| (define-minor-mode alchemist-help-minor-mode | |||
| "Minor mode for displaying elixir help." | |||
| :group 'alchemist-help | |||
| :keymap '(("q" . quit-window) | |||
| ("e" . alchemist-help-search-at-point) | |||
| ("m" . alchemist-help-search-marked-region) | |||
| ("s" . alchemist-help) | |||
| ("h" . alchemist-help-history) | |||
| ("?" . alchemist-help-minor-mode-key-binding-summary))) | |||
| (defun alchemist-help (search) | |||
| "Load Elixir documentation for SEARCH." | |||
| (interactive | |||
| (list (completing-read | |||
| "Elixir help: " | |||
| (alchemist-help--get-modules) | |||
| nil | |||
| nil | |||
| nil))) | |||
| (alchemist-help--execute (if (string-match-p "\\.$" search) | |||
| search | |||
| (concat search ".")))) | |||
| (defun alchemist-help-history (search) | |||
| "Load Elixir from the documentation history for SEARCH." | |||
| (interactive | |||
| (list | |||
| (completing-read "Elixir help history: " alchemist-help-search-history nil nil ""))) | |||
| (alchemist-help--execute-without-complete search)) | |||
| (provide 'alchemist-help) | |||
| ;;; alchemist-help.el ends here | |||
| @ -0,0 +1,50 @@ | |||
| ;;; 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 | |||
| @ -0,0 +1,171 @@ | |||
| ;;; 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) | |||
| (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'.") | |||
| (define-derived-mode alchemist-iex-mode comint-mode "Alchemist-IEx" | |||
| "Major mode for interacting with an Elixir IEx process." | |||
| nil "Alchemist-IEx" | |||
| (set (make-local-variable 'comint-prompt-regexp) | |||
| "^iex(\\([0-9]+\\|[a-zA-Z_@]+\\))> ") | |||
| (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))) | |||
| ;;;###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 | |||
| @ -0,0 +1,60 @@ | |||
| ;;; alchemist-message.el --- Internal message 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: | |||
| ;; Internal message functionality. | |||
| ;;; Code: | |||
| (defgroup alchemist-message nil | |||
| "Internal message functionality." | |||
| :prefix "alchemist-message-" | |||
| :group 'alchemist) | |||
| ;; Variables | |||
| (defvar alchemist-message--buffer-name "*alchemist message*") | |||
| (defun alchemist-message (message) | |||
| (alchemist-message--initialize-buffer message)) | |||
| (defun alchemist-message--initialize-buffer (message) | |||
| (display-buffer (get-buffer-create alchemist-message--buffer-name)) | |||
| (with-current-buffer alchemist-message--buffer-name | |||
| (let ((inhibit-read-only t) | |||
| (buffer-undo-list t)) | |||
| (erase-buffer) | |||
| (insert message) | |||
| (goto-char (point-min)) | |||
| (ansi-color-apply-on-region (point-min) (point-max)) | |||
| (read-only-mode) | |||
| (alchemist-message-mode 1)))) | |||
| (define-minor-mode alchemist-message-mode | |||
| "Minor mode for displaying alchemist messages" | |||
| :group 'alchemist-message | |||
| :lighter " alchemist-msg" | |||
| :keymap '(("q" . quit-window))) | |||
| (provide 'alchemist-message) | |||
| ;;; alchemist-message.el ends here | |||
| @ -0,0 +1,174 @@ | |||
| ;;; 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) | |||
| (defvar alchemist-mix-buffer-name "*mix*" | |||
| "Name of the mix output buffer.") | |||
| (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.") | |||
| (defun alchemist-mix--completing-read (prompt cmdlist) | |||
| (completing-read prompt cmdlist nil t nil nil (car cmdlist))) | |||
| (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)))) | |||
| (defun alchemist-mix-test () | |||
| "Run the whole elixir test suite." | |||
| (interactive) | |||
| (alchemist-mix-execute (list "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-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 (list "test" (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)))) | |||
| (defun alchemist-mix-compile (command) | |||
| "Compile the whole elixir project." | |||
| (interactive "Mmix compile: ") | |||
| (alchemist-mix-execute (list "compile" command))) | |||
| (defun alchemist-mix-run (command) | |||
| "Runs the given file or expression in the context of the application." | |||
| (interactive "Mmix run: ") | |||
| (alchemist-mix-execute (list "run" command))) | |||
| (defun alchemist-mix-deps-with-prompt (command) | |||
| "Prompt for mix deps commands." | |||
| (interactive | |||
| (list (alchemist-mix--completing-read "mix deps: " alchemist-mix--deps-commands))) | |||
| (alchemist-mix-execute (list command))) | |||
| (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"))))) | |||
| (defun alchemist-mix (command) | |||
| "Prompt for mix commands." | |||
| (interactive | |||
| (list (alchemist-mix--completing-read "mix: " (alchemist-mix--commands)))) | |||
| (let ((command (read-string "mix " (concat command " ")))) | |||
| (alchemist-mix-execute (list command)))) | |||
| (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)))) | |||
| (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))) | |||
| (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))) | |||
| (defun alchemist-mix-hex-search (command) | |||
| "Display packages matching the given search query." | |||
| (interactive "Mmix hex.search: ") | |||
| (alchemist-mix-execute (list "hex.search" command))) | |||
| (defun alchemist-mix-help (command) | |||
| "Show help output for a specific mix command." | |||
| (interactive "Mmix help: ") | |||
| (alchemist-mix-execute (list "help" command))) | |||
| (defun alchemist-mix-execute (cmdlist) | |||
| "Run a mix command." | |||
| (interactive "Mmix: ") | |||
| (let ((old-directory default-directory)) | |||
| (alchemist-project--establish-root-directory) | |||
| (alchemist-buffer-run (alchemist-utils--build-runner-cmdlist (list alchemist-mix-command cmdlist)) | |||
| alchemist-mix-buffer-name) | |||
| (cd old-directory))) | |||
| (provide 'alchemist-mix) | |||
| ;;; alchemist-mix.el ends here | |||
| @ -0,0 +1,7 @@ | |||
| (define-package "alchemist" "20150201.2244" "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: | |||
| @ -0,0 +1,200 @@ | |||
| ;;; 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) | |||
| (require 'json) | |||
| (defgroup alchemist-project nil | |||
| "API to identify Elixir mix projects." | |||
| :prefix "alchemist-help-" | |||
| :group 'alchemist) | |||
| (defcustom alchemist-project-config-filename ".alchemist" | |||
| "Name of the file which holds the Elixir project setup." | |||
| :type 'string | |||
| :group 'alchemist) | |||
| (defcustom alchemist-project-compile-when-needed nil | |||
| "When `t', it compiles the Elixir project codebase when needed. | |||
| For example: | |||
| If documentation lookup or completion for code is made, it first tries to | |||
| compile the current Elixir project codebase. This makes sure that the | |||
| documentation and completion is always up to date with the codebase. | |||
| Please be aware that when the compilation fails, no documentation or | |||
| completion will be work. | |||
| " | |||
| :type 'string | |||
| :group 'alchemist) | |||
| (defun alchemist-project-toggle-compile-when-needed () | |||
| "" | |||
| (interactive) | |||
| (if alchemist-project-compile-when-needed | |||
| (setq alchemist-project-compile-when-needed nil) | |||
| (setq alchemist-project-compile-when-needed t)) | |||
| (if alchemist-project-compile-when-needed | |||
| (message "Compilation of project when needed is enabled") | |||
| (message "Compilation of project when needed is disabled"))) | |||
| (defun alchemist-project--load-compile-when-needed-setting () | |||
| (let ((config (gethash "compile-when-needed" (alchemist-project-config)))) | |||
| (if config | |||
| (intern config) | |||
| alchemist-project-compile-when-needed))) | |||
| (defun alchemist-project--config-filepath () | |||
| "Return the path to the config file." | |||
| (format "%s/%s" | |||
| (alchemist-project-root) | |||
| alchemist-project-config-filename)) | |||
| (defun alchemist-project--config-exists-p () | |||
| "Check if project config file exists." | |||
| (file-exists-p (alchemist-project--config-filepath))) | |||
| (defun alchemist-project-config () | |||
| "Return the current Elixir project configs." | |||
| (let* ((json-object-type 'hash-table) | |||
| (config (if (alchemist-project--config-exists-p) | |||
| (json-read-from-string | |||
| (with-temp-buffer | |||
| (insert-file-contents (alchemist-project--config-filepath)) | |||
| (buffer-string))) | |||
| (make-hash-table :test 'equal)))) | |||
| config)) | |||
| (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-open-tests-for-current-file () | |||
| "Opens the appropriate test file for the current buffer file | |||
| in a new window." | |||
| (interactive) | |||
| (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) | |||
| (find-file-other-window 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/\" | grep -v \"/.yardoc/\"")))))))) | |||
| (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 | |||
| @ -0,0 +1,84 @@ | |||
| ;;; 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)) | |||
| (provide 'alchemist-utils) | |||
| ;;; alchemist-utils.el ends here | |||
| @ -0,0 +1,132 @@ | |||
| ;;; alchemist.el --- Elixir tooling integration into Emacs | |||
| ;; Copyright © 2014-2015 Samuel Tonini | |||
| ;; | |||
| ;; Author: Samuel Tonini <tonini.samuel@gmail.com> | |||
| ;; URL: http://www.github.com/tonini/alchemist.el | |||
| ;; Version: 0.14.0-cvs | |||
| ;; Package-Requires: ((emacs "24")) | |||
| ;; Keywords: languages, mix, elixir, elixirc, hex | |||
| ;; This file is not part of GNU Emacs. | |||
| ;; This program is free software: you can redistribute it and/or modify | |||
| ;; it under the terms of the GNU General Public License as published by | |||
| ;; the Free Software Foundation, either version 3 of the License, or | |||
| ;; (at your option) any later version. | |||
| ;; This program is distributed in the hope that it will be useful, | |||
| ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| ;; GNU General Public License for more details. | |||
| ;; You should have received a copy of the GNU General Public License | |||
| ;; along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
| ;;; Commentary: | |||
| ;; Alchemist integrate Elixir's tooling into Emacs | |||
| ;;; Code: | |||
| (defgroup alchemist nil | |||
| "Elixir Tooling Integration Into Emacs." | |||
| :prefix "alchemist-" | |||
| :group 'applications | |||
| :link '(url-link :tag "Github" "https://github.com/tonini/alchemist.el") | |||
| :link '(emacs-commentary-link :tag "Commentary" "alchemist")) | |||
| (require 'alchemist-utils) | |||
| (require 'alchemist-project) | |||
| (require 'alchemist-buffer) | |||
| (require 'alchemist-compile) | |||
| (require 'alchemist-execute) | |||
| (require 'alchemist-mix) | |||
| (require 'alchemist-hooks) | |||
| (require 'alchemist-help) | |||
| (require 'alchemist-complete) | |||
| (require 'alchemist-message) | |||
| (require 'alchemist-iex) | |||
| (require 'alchemist-eval) | |||
| (require 'alchemist-goto) | |||
| (eval-after-load 'company | |||
| '(progn | |||
| (require 'alchemist-company))) | |||
| (defun alchemist-mode-hook () | |||
| "Hook which enables `alchemist-mode'" | |||
| (alchemist-mode 1)) | |||
| (defvar alchemist--version "0.14.0-cvs") | |||
| ;;;###autoload | |||
| (defun alchemist-version (&optional show-version) | |||
| "Display Alchemist's version." | |||
| (interactive) | |||
| (message "Alchemist %s" (replace-regexp-in-string "-cvs" "snapshot" alchemist--version))) | |||
| (defvar alchemist-mode-map | |||
| (let ((map (make-sparse-keymap))) | |||
| (define-key map (kbd "C-c a t") 'alchemist-mix-test) | |||
| (define-key map (kbd "C-c a m t f") 'alchemist-mix-test-file) | |||
| (define-key map (kbd "C-c a m t b") 'alchemist-mix-test-this-buffer) | |||
| (define-key map (kbd "C-c a m t .") 'alchemist-mix-test-at-point) | |||
| (define-key map (kbd "C-c a c c") 'alchemist-compile) | |||
| (define-key map (kbd "C-c a c f") 'alchemist-compile-file) | |||
| (define-key map (kbd "C-c a c b") 'alchemist-compile-this-buffer) | |||
| (define-key map (kbd "C-c a e e") 'alchemist-execute) | |||
| (define-key map (kbd "C-c a e f") 'alchemist-execute-file) | |||
| (define-key map (kbd "C-c a e b") 'alchemist-execute-this-buffer) | |||
| (define-key map (kbd "C-c a h h") 'alchemist-help) | |||
| (define-key map (kbd "C-c a h e") 'alchemist-help-search-at-point) | |||
| (define-key map (kbd "C-c a h m") 'alchemist-help-search-marked-region) | |||
| (define-key map (kbd "C-c a p f") 'alchemist-project-find-test) | |||
| (define-key map (kbd "C-c a p t") 'alchemist-project-open-tests-for-current-file) | |||
| (define-key map (kbd "C-c a i i") 'alchemist-iex-run) | |||
| (define-key map (kbd "C-c a i p") 'alchemist-iex-project-run) | |||
| (define-key map (kbd "C-c a i l") 'alchemist-iex-send-current-line) | |||
| (define-key map (kbd "C-c a i c") 'alchemist-iex-send-current-line-and-go) | |||
| (define-key map (kbd "C-c a i r") 'alchemist-iex-send-region) | |||
| (define-key map (kbd "C-c a i m") 'alchemist-iex-send-region-and-go) | |||
| (define-key map (kbd "C-c a i b") 'alchemist-iex-compile-this-buffer) | |||
| (define-key map (kbd "C-c a v l") 'alchemist-eval-current-line) | |||
| (define-key map (kbd "C-c a v k") 'alchemist-eval-print-current-line) | |||
| (define-key map (kbd "C-c a v j") 'alchemist-eval-quoted-current-line) | |||
| (define-key map (kbd "C-c a v h") 'alchemist-eval-print-quoted-current-line) | |||
| (define-key map (kbd "C-c a v o") 'alchemist-eval-region) | |||
| (define-key map (kbd "C-c a v i") 'alchemist-eval-print-region) | |||
| (define-key map (kbd "C-c a v u") 'alchemist-eval-quoted-region) | |||
| (define-key map (kbd "C-c a v y") 'alchemist-eval-print-quoted-region) | |||
| (define-key map (kbd "C-c a v q") 'alchemist-eval-buffer) | |||
| (define-key map (kbd "C-c a v w") 'alchemist-eval-print-buffer) | |||
| (define-key map (kbd "C-c a v e") 'alchemist-eval-quoted-buffer) | |||
| (define-key map (kbd "C-c a v r") 'alchemist-eval-print-quoted-buffer) | |||
| (define-key map (kbd "M-.") 'alchemist-goto-definition-at-point) | |||
| (define-key map (kbd "M-,") 'alchemist-goto-jump-back) | |||
| map) | |||
| "The keymap used when `alchemist-mode' is active.") | |||
| ;;;###autoload | |||
| (define-minor-mode alchemist-mode | |||
| "Toggle alchemist mode. | |||
| Key bindings: | |||
| \\{alchemist-mode-map}" | |||
| nil | |||
| ;; The indicator for the mode line. | |||
| " alchemist" | |||
| :group 'alchemist | |||
| :global nil | |||
| :keymap 'alchemist-mode-map | |||
| (cond (alchemist-mode | |||
| (alchemist-buffer-initialize-modeline)) | |||
| (t | |||
| (alchemist-buffer-reset-modeline)))) | |||
| (add-hook 'elixir-mode-hook 'alchemist-mode-hook) | |||
| (provide 'alchemist) | |||
| ;;; alchemist.el ends here | |||
| @ -0,0 +1,28 @@ | |||
| ;;; bison-mode-autoloads.el --- automatically extracted autoloads | |||
| ;; | |||
| ;;; Code: | |||
| (add-to-list 'load-path (or (file-name-directory #$) (car load-path))) | |||
| ;;;### (autoloads nil "bison-mode" "bison-mode.el" (21739 37356 0 | |||
| ;;;;;; 0)) | |||
| ;;; Generated autoloads from bison-mode.el | |||
| (add-to-list 'auto-mode-alist '("\\.y\\'" . bison-mode)) | |||
| (add-to-list 'auto-mode-alist '("\\.l\\'" . bison-mode)) | |||
| (add-to-list 'auto-mode-alist '("\\.jison\\'" . jison-mode)) | |||
| (autoload 'bison-mode "bison-mode" "\ | |||
| Major mode for editing bison/yacc files. | |||
| \(fn)" t nil) | |||
| ;;;*** | |||
| ;; Local Variables: | |||
| ;; version-control: never | |||
| ;; no-byte-compile: t | |||
| ;; no-update-autoloads: t | |||
| ;; End: | |||
| ;;; bison-mode-autoloads.el ends here | |||
| @ -0,0 +1 @@ | |||
| (define-package "bison-mode" "20141119.43" "Major mode for editing bison, yacc and lex files." 'nil :keywords '("bison-mode" "yacc-mode")) | |||
| @ -0,0 +1,908 @@ | |||
| ;;; bison-mode.el --- Major mode for editing bison, yacc and lex files. | |||
| ;; Copyright (C) 1998 Eric Beuscher | |||
| ;; | |||
| ;; Author: Eric Beuscher <beuscher@eecs.tulane.edu> | |||
| ;; Created: 2 Feb 1998 | |||
| ;; Version: 20141119.43 | |||
| ;; X-Original-Version: 0.2 | |||
| ;; Keywords: bison-mode, yacc-mode | |||
| ;; 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 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. | |||
| ;;; Commentary: | |||
| ;;;; I wrote this since I saw one mode for yacc files out there roaming the | |||
| ;;;; world. I was daunted by the fact the it was written in 1990, and Emacs | |||
| ;;;; has evolved so much since then (this I assume based on its evolution since | |||
| ;;;; i started using it). So I figured if i wanted one, I should make it | |||
| ;;;; myself. Please excuse idiosyncrasies, as this was my first major mode | |||
| ;;;; of this kind. The indentation code may be a bit weird, I am not sure, | |||
| ;;;; it was my first go at doing Emacs indentation, so I look at how other | |||
| ;;;; modes did it, but then basically did what I thought was right | |||
| ;;;; I hope this is useful to other hackers, and happy Bison/Yacc hacking | |||
| ;;;; If you have ideas/suggestions/problems with this code, I can be reached at | |||
| ;;;; beuscher@eecs.tulane.edu | |||
| ;;;; Eric --- Sat Mar 7 1:40:20 CDT 1998 | |||
| ;;;; Bison Sections: | |||
| ;;;; there are five sections to a bison file (if you include the area above the | |||
| ;;;; C declarations section. most everything in this file either does | |||
| ;;;; actions based on which section you are deemed to be in, or based on an | |||
| ;;;; assumption that the function will only be called from certain sections. | |||
| ;;;; the function `bison--section-p' is the section parser | |||
| ;;;; Indentation: | |||
| ;;;; indentations are done based on the section of code you are in. there is | |||
| ;;;; a procedure `bison--within-braced-c-expression-p' that checks for being in | |||
| ;;;; C code. if you are within c-code, indentations should occur based on | |||
| ;;;; how you have your C indentation set up. i am pretty sure this is the | |||
| ;;;; case. | |||
| ;;;; there are four variables, which control bison indentation within either | |||
| ;;;; the bison declarations section or the bison grammar section | |||
| ;;;; `bison-rule-separator-column' | |||
| ;;;; `bison-rule-separator-column' | |||
| ;;;; `bison-decl-type-column' | |||
| ;;;; `bison-decl-token-column' | |||
| ;;;; flaw: indentation works on a per-line basis, unless within braced C sexp, | |||
| ;;;; i should fix this someday | |||
| ;;;; and to make matters worse, i never took out c-indent-region, so that is | |||
| ;;;; still the state of the `indent-region-function' variable | |||
| ;;;; Electricity: | |||
| ;;;; by default, there are electric -colon, -pipe, -open-brace, -close-brace, | |||
| ;;;; -semicolon, -percent, -less-than, -greater-than | |||
| ;;;; the indentation caused by these work closely with the 4 indentation | |||
| ;;;; variables mentioned above. | |||
| ;;;; any of these can be turned off individually by setting the appropriate | |||
| ;;;; `bison-electric-...' variable. or all of them can be turned off by | |||
| ;;;; setting `bison-all-electricity-off' | |||
| ;;;; todo: should make available a way to use C-electricity if in C sexps | |||
| ;;; Code: | |||
| (require 'cc-mode) | |||
| ;;;###autoload | |||
| (add-to-list 'auto-mode-alist '("\\.y\\'" . bison-mode)) | |||
| ;;;###autoload | |||
| (add-to-list 'auto-mode-alist '("\\.l\\'" . bison-mode)) | |||
| ;;;###autoload | |||
| (add-to-list 'auto-mode-alist '("\\.jison\\'" . jison-mode)) | |||
| ;; *************** internal vars *************** | |||
| (defvar bison--declarers '("%union" "%token" "%type" | |||
| "%left" "%right" "%nonassoc") | |||
| "commands which can declare a token or state type") | |||
| (defvar bison--word-constituent-re "\\(\\sw\\|_\\)") | |||
| (defvar bison--production-re | |||
| (concat "^" bison--word-constituent-re "+:")) | |||
| (defvar bison--pre-c-decls-section 0 | |||
| "section before c-declarations-section, if that section exists") | |||
| (defvar bison--c-decls-section 1 | |||
| "section denoted by %{ and $} for c-declarations at the top of a bison file") | |||
| (defvar bison--bison-decls-section 2 | |||
| "section before the rules section") | |||
| (defvar bison--grammar-rules-section 3 | |||
| "section delimited by %%'s where productions and rules are enumerated") | |||
| (defvar bison--c-code-section 4 | |||
| "section after the second %% where c-code can be placed") | |||
| (defvar bison--c-decls-section-opener "%{") | |||
| (defvar bison--c-decls-section-closer "%}") | |||
| (defvar bison--grammar-rules-section-delimeter "%%") | |||
| ;; *************** user-definable vars *************** | |||
| (defvar bison-rule-separator-column 8 | |||
| "column for rule and production separators \"|\" and \";\"") | |||
| (defvar bison-rule-enumeration-column 16 | |||
| "column for beginning enumeration of a production's rules") | |||
| (defvar bison-decl-type-column 8 | |||
| "columnn in which tokens' and states' types should be when declared") | |||
| (defvar bison-decl-token-column 24 | |||
| "column in which tokens and states are listed when declared, | |||
| as with %token, %type, ...") | |||
| (defvar bison-all-electricity-off nil | |||
| "non-nil means all electric keys will be disabled, | |||
| nil means that a bison-electric-* key will be on or off based on the individual | |||
| key's electric variable") | |||
| ;;; i know lisp has the dual name spaces, but i find it more aesthetically | |||
| ;;; pleasing to not take advantage of that | |||
| (defvar bison-electric-colon-v t | |||
| "non-nil means use an electric colon") | |||
| (defvar bison-electric-pipe-v t | |||
| "non-nil means use an electric pipe") | |||
| (defvar bison-electric-open-brace-v t | |||
| "non-nil means use an electric open-brace") | |||
| (defvar bison-electric-close-brace-v t | |||
| "non-nil means use an electric close-brace") | |||
| (defvar bison-electric-semicolon-v t | |||
| "non-nil means use an electric semicolon") | |||
| (defvar bison-electric-percent-v t | |||
| "non-nil means use an electric percent") | |||
| (defvar bison-electric-less-than-v t | |||
| "non-nil means use an electric less-than") | |||
| (defvar bison-electric-greater-than-v t | |||
| "non-nil means use an electric greater-than") | |||
| (defconst bison-font-lock-keywords | |||
| (append | |||
| (list | |||
| (cons (concat "^\\(" (regexp-opt bison--declarers) "\\)") | |||
| '(1 font-lock-keyword-face)) | |||
| ) | |||
| c-font-lock-keywords) | |||
| "Default expressions to highlight in Bison mode") | |||
| ;; *************** utilities *************** | |||
| (defun just-no-space () | |||
| "Delete all spaces and tabs around point, leaving no spaces." | |||
| (interactive "*") | |||
| (skip-chars-backward " \t") | |||
| (delete-region (point) (progn (skip-chars-forward " \t") (point))) | |||
| t) | |||
| (defun previous-white-space-p () | |||
| "return t if there is whitespace between the beginning of the line and the | |||
| current (point)" | |||
| (save-excursion | |||
| (let ((current-point (point))) | |||
| (beginning-of-line) | |||
| (if (re-search-forward "\\s " current-point t) | |||
| t | |||
| nil)))) | |||
| (defun previous-non-ws-p () | |||
| "return t if there are non-whitespace characters between beginning of line | |||
| and \(point\)" | |||
| (save-excursion | |||
| (let ((current-point (point))) | |||
| (beginning-of-line) | |||
| (re-search-forward "[^ \t]" current-point t) | |||
| ))) | |||
| (defun following-non-ws-p () | |||
| "return t if there are non-whitespace characters on the line" | |||
| (save-excursion | |||
| (let ((current-point (point))) | |||
| (end-of-line) | |||
| (re-search-backward "[^ \t]+" current-point t) | |||
| ))) | |||
| (defun line-of-whitespace-p () | |||
| "return t if the line consists of nothiing but whitespace, nil otherwise" | |||
| (save-excursion | |||
| (let ((eol (progn (end-of-line) (point)))) | |||
| (beginning-of-line) ;; should already be there anyway | |||
| (not (re-search-forward "[^ \t\n]" eol t))))) | |||
| ;; *************** bison-mode *************** | |||
| ;;;###autoload | |||
| (define-derived-mode bison-mode c-mode "Bison" | |||
| "Major mode for editing bison/yacc files." | |||
| ;; try to set the indentation correctly | |||
| (setq c-basic-offset 4) | |||
| (c-set-offset 'knr-argdecl-intro 0) | |||
| ;; remove auto and hungry anything | |||
| (c-toggle-auto-hungry-state -1) | |||
| (c-toggle-auto-newline -1) | |||
| (c-toggle-hungry-state -1) | |||
| (use-local-map bison-mode-map) | |||
| (define-key bison-mode-map ":" 'bison-electric-colon) | |||
| (define-key bison-mode-map "|" 'bison-electric-pipe) | |||
| (define-key bison-mode-map "{" 'bison-electric-open-brace) | |||
| (define-key bison-mode-map "}" 'bison-electric-close-brace) | |||
| (define-key bison-mode-map ";" 'bison-electric-semicolon) | |||
| (define-key bison-mode-map "%" 'bison-electric-percent) | |||
| (define-key bison-mode-map "<" 'bison-electric-less-than) | |||
| (define-key bison-mode-map ">" 'bison-electric-greater-than) | |||
| (define-key bison-mode-map [tab] 'bison-indent-line) | |||
| (make-local-variable 'indent-line-function) | |||
| (setq indent-line-function 'bison-indent-new-line) | |||
| (make-local-variable 'comment-start) | |||
| (make-local-variable 'comment-end) | |||
| (setq comment-start "/*" | |||
| comment-end "*/") | |||
| (make-local-variable 'font-lock-keywords) | |||
| (setq font-lock-keywords nil) | |||
| (set (make-local-variable 'font-lock-defaults) '(bison-font-lock-keywords))) | |||
| ;; *************** section parsers *************** | |||
| (defun bison--section-p () | |||
| "Return the section that user is currently in" | |||
| (save-excursion | |||
| (let ((bound (point))) | |||
| (goto-char (point-min)) | |||
| (bison--section-p-helper bound)))) | |||
| (defun bison--section-p-helper (bound) | |||
| (if (re-search-forward | |||
| (concat "^" bison--c-decls-section-opener) | |||
| bound t) | |||
| (if (re-search-forward | |||
| (concat "^" bison--c-decls-section-closer) | |||
| bound t) | |||
| (if (re-search-forward | |||
| (concat "^" bison--grammar-rules-section-delimeter) | |||
| bound t) | |||
| (if (re-search-forward | |||
| (concat "^" bison--grammar-rules-section-delimeter) | |||
| bound t) | |||
| bison--c-code-section | |||
| bison--grammar-rules-section) | |||
| bison--bison-decls-section) | |||
| bison--c-decls-section) | |||
| (if (re-search-forward | |||
| (concat "^" bison--grammar-rules-section-delimeter) | |||
| bound t) | |||
| (if (re-search-forward | |||
| (concat "^" bison--grammar-rules-section-delimeter) | |||
| bound t) | |||
| bison--c-code-section | |||
| bison--grammar-rules-section) | |||
| (if (re-search-forward | |||
| (concat "^" bison--c-decls-section-opener) | |||
| nil t) | |||
| bison--pre-c-decls-section | |||
| (if (re-search-forward | |||
| (concat "^" bison--grammar-rules-section-delimeter) | |||
| nil t) | |||
| bison--bison-decls-section | |||
| bison--pre-c-decls-section))))) | |||
| ;; *************** syntax parsers *************** | |||
| (defun bison--production-p () | |||
| "return t if the \(point\) rests immediately after a production" | |||
| (save-excursion | |||
| (let ((current-point (point))) | |||
| (beginning-of-line) | |||
| (let ((position (re-search-forward | |||
| bison--production-re current-point t))) | |||
| (and position | |||
| (not (previous-white-space-p)) | |||
| (= position current-point)))))) | |||
| (defun bison--find-production-opener () | |||
| "return and goto the point of the nearest production opener above \(point\)" | |||
| (re-search-backward bison--production-re nil t)) | |||
| (defun bison--find-next-production () | |||
| "return the position of the beginning of the next production, | |||
| or nil if there isnt one" | |||
| (save-excursion | |||
| (if (re-search-forward bison--production-re nil t) | |||
| (progn | |||
| (beginning-of-line) | |||
| (point)) | |||
| nil))) | |||
| (defun bison--find-grammar-end () | |||
| "return the position of the end of the grammar rules (assuming we are within | |||
| the grammar rules section), or nil if there isnt one" | |||
| (save-excursion | |||
| (if (re-search-forward | |||
| (concat "^" bison--grammar-rules-section-delimeter) | |||
| nil t) | |||
| (progn | |||
| (beginning-of-line) | |||
| (point)) | |||
| nil))) | |||
| (defun bison--find-grammar-begin () | |||
| "return the position of the beginning of the grammar rules (assuming we are | |||
| within the grammar rules section), or nil if there isnt one" | |||
| (save-excursion | |||
| (if (re-search-backward | |||
| (concat "^" bison--grammar-rules-section-delimeter) | |||
| nil t) | |||
| (point) | |||
| nil))) | |||
| (defun bison--within-started-production-p () | |||
| "is used by bison-electric-* functions to determine actions | |||
| return t if within a production, nil if not | |||
| a point is within a production if there is some non whitespace text before | |||
| either the beginnings of another production or the end of the grammar rules" | |||
| (save-excursion | |||
| (let ((bound (cond ((bison--find-next-production)) | |||
| ((bison--find-grammar-end)) | |||
| (t nil)))) | |||
| (if bound | |||
| (let ((sval (re-search-forward | |||
| (concat "\\(\\s \\|" ;; whitespace or | |||
| ;; comments | |||
| (regexp-quote comment-start) | |||
| "\\(.\\|\n\\)*" ;; comment body | |||
| (regexp-quote comment-end) | |||
| "\\)+") ;; end or | |||
| bound t))) | |||
| (if sval | |||
| (not (= sval bound)) | |||
| nil)) | |||
| nil)))) | |||
| (defun bison--within-some-sexp-p (starter ender) | |||
| "return t if the \(point\) is within the sexp marked by the re's STARTER and | |||
| ENDER" | |||
| (save-excursion | |||
| (let ((current-point (point))) | |||
| (if (re-search-backward starter nil t) ;; find nearest starter | |||
| ;; look for ender, if found, then not within sexp | |||
| (progn | |||
| (goto-char (match-end 0)) | |||
| (not (re-search-forward ender current-point t))))))) | |||
| (defun bison--within-c-comment-p () | |||
| "return t if the point is within a c comment delimited by \"/*\" \"*/\"" | |||
| (bison--within-some-sexp-p (regexp-quote comment-start) | |||
| (regexp-quote comment-end))) | |||
| (defun bison--within-string-p (&optional point) | |||
| " | |||
| start from the beginning of the buffer and toggle state as un-escaped \"'s are | |||
| found." | |||
| (let ((point (or point (point))) | |||
| (in-p nil)) | |||
| (save-excursion | |||
| (goto-char (point-min)) | |||
| (while (re-search-forward "[^\\]\"" point t) | |||
| (setq in-p (not in-p))) | |||
| in-p))) | |||
| ;;; bison--within-braced-c-expression-p | |||
| ;;; new and improved, no more recursion, does not break when literal strings | |||
| ;;; contain un-matched braces | |||
| (defun bison--within-braced-c-expression-p (section) | |||
| "return t if the point is within an sexp delimited by braces \({,}\) | |||
| " | |||
| (save-excursion | |||
| (bison--within-braced-c-expression-p-h section (point)))) | |||
| (defun bison--within-braced-c-expression-p-h (section low-pt) | |||
| " | |||
| Notes: | |||
| save excursion is done higher up, so i dont concern myself here. | |||
| " | |||
| (cond ((= section bison--pre-c-decls-section) nil) | |||
| ((= section bison--c-decls-section) | |||
| (let ((opener (save-excursion (search-backward "%{")))) | |||
| (bison--within-braced-c-expression-p-h-h opener low-pt))) | |||
| ((= section bison--bison-decls-section) | |||
| (let ((opener (save-excursion | |||
| (or (search-backward "%}" nil t) | |||
| (point-min))))) | |||
| (bison--within-braced-c-expression-p-h-h opener low-pt))) | |||
| ((= section bison--grammar-rules-section) | |||
| (let ((opener (save-excursion (bison--find-production-opener)))) | |||
| (if opener | |||
| (bison--within-braced-c-expression-p-h-h opener low-pt) | |||
| nil))) | |||
| ((= section bison--c-code-section) | |||
| t))) | |||
| (defun bison--within-braced-c-expression-p-h-h (high-pt low-pt) | |||
| " | |||
| Notes: | |||
| HIGH-PT goes toward (point-min), LOW-PT goes toward (point-max) | |||
| save excursion is done higher up, so i dont concern myself here. | |||
| " | |||
| (let ((pt (point))) | |||
| (let ((success nil) (count 1) (done nil)) | |||
| ;; loop until open brace found, that is not in comment or string literal | |||
| (while (and (not done) | |||
| (re-search-backward "[^%]{" high-pt t count)) ;find nearest | |||
| ;starter | |||
| (goto-char (match-end 0)) | |||
| (if (or (bison--within-c-comment-p) | |||
| (bison--within-string-p)) | |||
| (setq count (+ count 1)) | |||
| (progn | |||
| (setq success t) | |||
| (setq done t)))) | |||
| (if success | |||
| (let ((end-pt | |||
| (condition-case nil | |||
| (progn | |||
| (backward-char) | |||
| (forward-sexp) | |||
| (point)) | |||
| (error nil)))) | |||
| (if end-pt | |||
| (if (> end-pt low-pt) | |||
| t ; then in braced-c-exp | |||
| nil) | |||
| t)) ; if no sexp close brace, then w/in | |||
| nil)))) | |||
| (defun bison--bison-decl-opener-p (bol eol) | |||
| "return t if the current line is a bison declaration starter | |||
| \(i.e. has a %type, %token, %right, ...\)" | |||
| (save-excursion | |||
| (goto-char bol) | |||
| (re-search-forward | |||
| (concat "^" (regexp-opt (copy-sequence bison--declarers))) eol t))) | |||
| (defun bison--production-opener-p (bol eol) | |||
| "return t if the current line is a line that introduces a new production" | |||
| (save-excursion | |||
| (goto-char bol) | |||
| (re-search-forward bison--production-re eol t))) | |||
| (defun bison--find-bison-semicolon () | |||
| "return the position of next semicolon not within braces, nil otherwise" | |||
| (save-excursion | |||
| (if (search-forward ";" nil t) | |||
| (if (not (bison--within-braced-c-expression-p (bison--section-p))) | |||
| (point) | |||
| (bison--find-bison-semicolon)) | |||
| nil))) | |||
| (defun bison--within-production-body-p (section) | |||
| "return t if the \(point\) is within the body of a production | |||
| this procedure will fail if it is in a production header" | |||
| (save-excursion | |||
| (if (= section bison--grammar-rules-section) | |||
| (let ((current-point (point))) | |||
| (if (re-search-backward bison--production-re nil t) | |||
| t | |||
| nil)) | |||
| nil))) | |||
| (defun bison--production-alternative-p (bol eol section) | |||
| "return t if the current line contains a \"|\" used to designate a rule | |||
| alternative" | |||
| (save-excursion | |||
| (goto-char bol) | |||
| (if (search-forward "|" eol t) | |||
| (not (bison--within-braced-c-expression-p section)) | |||
| nil))) | |||
| ;; *************** indent functions *************** | |||
| (defun bison--handle-indent-c-sexp (section indent-column bol) | |||
| (let* ((o-brace (re-search-backward "[^%]{" bol t)) | |||
| ) | |||
| (if o-brace | |||
| (if (save-excursion | |||
| (goto-char o-brace) | |||
| (bison--within-braced-c-expression-p section)) | |||
| (c-indent-line) | |||
| (if (= (current-indentation) o-brace) ;; if o-brace is first char | |||
| (if (not (= o-brace indent-column)) ;; but not in right spot | |||
| (progn | |||
| (back-to-indentation) | |||
| (just-no-space) | |||
| (indent-to-column indent-column)) | |||
| ;; else all is good | |||
| ) | |||
| ;; else, non-ws before o-brace, leave it alone | |||
| )) | |||
| (c-indent-line)))) | |||
| (defun bison-indent-new-line (&optional c-sexp) | |||
| "Indent a fresh line of bison code | |||
| assumes indenting a new line, i.e. at column 0 | |||
| " | |||
| (interactive) | |||
| (let* ((section (bison--section-p)) | |||
| (c-sexp (or c-sexp (bison--within-braced-c-expression-p section))) | |||
| ) | |||
| (cond | |||
| (c-sexp | |||
| (cond | |||
| ((= section bison--grammar-rules-section) | |||
| (c-indent-line | |||
| (save-excursion | |||
| (forward-line -1) | |||
| (let ((bol (save-excursion (beginning-of-line) (point))) | |||
| (eol (save-excursion (end-of-line) (point)))) | |||
| (if (bison--production-opener-p bol eol) | |||
| (list | |||
| (cons 'defun-block-intro | |||
| (progn | |||
| (re-search-forward bison--production-re) ; SIGERR | |||
| (- (re-search-forward "[^ \t]") ; SIGERR | |||
| 1)))) | |||
| nil))))) | |||
| (t (c-indent-line)))) | |||
| ((= section bison--pre-c-decls-section) | |||
| (c-indent-line)) | |||
| ((= section bison--bison-decls-section) | |||
| (indent-to-column bison-decl-token-column)) | |||
| ((= section bison--grammar-rules-section) | |||
| (indent-to-column | |||
| (save-excursion | |||
| (let* ((bound (or (save-excursion (bison--find-production-opener)) | |||
| (bison--find-grammar-begin))) | |||
| (prev-semi (search-backward ";" bound t)) | |||
| ) | |||
| (if prev-semi | |||
| (if (bison--within-braced-c-expression-p section) ; CRACK | |||
| bison-rule-enumeration-column | |||
| 0) | |||
| (if (save-excursion (bison--find-production-opener)) | |||
| bison-rule-enumeration-column | |||
| 0)))))) | |||
| ((= section bison--c-code-section)) ;;leave-alone | |||
| ))) | |||
| (defun bison-indent-line () | |||
| "Indent a line of bison code." | |||
| (interactive) | |||
| (let* ((pos (- (point-max) (point))) | |||
| (reset-pt (function (lambda () | |||
| (if (> (- (point-max) pos) (point)) | |||
| (goto-char (- (point-max) pos)))))) | |||
| (bol (save-excursion (beginning-of-line) (point))) | |||
| (eol (save-excursion (end-of-line) (point))) | |||
| ) | |||
| (let* ((section (bison--section-p)) | |||
| (c-sexp (bison--within-braced-c-expression-p section)) | |||
| (ws-line (line-of-whitespace-p)) | |||
| ) | |||
| (cond | |||
| ;; if you are a line of whitespace, let indent-new-line take care of it | |||
| (ws-line | |||
| (bison-indent-new-line c-sexp)) | |||
| ((= section bison--pre-c-decls-section) | |||
| ;; leave things alone | |||
| ) | |||
| ((= section bison--c-decls-section) | |||
| (if c-sexp | |||
| (bison--handle-indent-c-sexp section 0 bol) | |||
| (if (not (= (current-indentation) 0)) | |||
| (progn | |||
| (back-to-indentation) | |||
| (just-no-space) | |||
| (funcall reset-pt))))) | |||
| ((= section bison--bison-decls-section) | |||
| (let ((opener (bison--bison-decl-opener-p bol eol))) | |||
| (cond | |||
| (opener | |||
| (goto-char opener) | |||
| (skip-chars-forward " \t" eol) | |||
| (if (looking-at "{") | |||
| (save-excursion | |||
| (if (following-non-ws-p) | |||
| (progn | |||
| (forward-char 1) | |||
| (just-no-space) | |||
| (newline) | |||
| (bison-indent-new-line t)))) | |||
| (let ((complete-type t)) | |||
| (if (looking-at "<") | |||
| (progn | |||
| (setq complete-type nil) | |||
| (if (not (= (current-column) bison-decl-type-column)) | |||
| (progn | |||
| (just-no-space) | |||
| (indent-to-column bison-decl-type-column)) | |||
| (and (re-search-forward | |||
| (concat "<" bison--word-constituent-re "+>") | |||
| eol t) | |||
| (setq complete-type t))))) | |||
| (and complete-type | |||
| (skip-chars-forward " \t" eol) | |||
| (looking-at | |||
| (concat "\\(" bison--word-constituent-re "\\|'\\)")) | |||
| (if (not (= (current-column) bison-decl-token-column)) | |||
| (progn | |||
| (just-no-space) | |||
| (indent-to-column bison-decl-token-column)))))) | |||
| (funcall reset-pt)) | |||
| (c-sexp | |||
| (bison--handle-indent-c-sexp section 0 bol)) | |||
| (t | |||
| (back-to-indentation) | |||
| ;; only tab in names, leave comments alone | |||
| (cond (;; put word-constiuents in bison-decl-token-column | |||
| (looking-at bison--word-constituent-re) | |||
| (if (not (= (current-column) bison-decl-token-column)) | |||
| (progn | |||
| (just-no-space) | |||
| (indent-to-column bison-decl-token-column)))) | |||
| ;; put/keep close-brace in the 0 column | |||
| ((looking-at "}") | |||
| (if (not (= (current-column) 0)) | |||
| (just-no-space))) | |||
| ;; leave comments alone | |||
| ((looking-at (regexp-quote comment-start)) nil) | |||
| ;; else do nothing | |||
| ) | |||
| (funcall reset-pt))))) | |||
| ((= section bison--grammar-rules-section) | |||
| (cond | |||
| ((bison--production-opener-p bol eol) | |||
| (beginning-of-line) | |||
| (re-search-forward bison--production-re);; SIGERR | |||
| (if (following-non-ws-p) | |||
| (if (> (current-column) bison-rule-enumeration-column) | |||
| (progn | |||
| (just-no-space) | |||
| (newline) | |||
| (indent-to-column bison-rule-enumeration-column)) | |||
| (save-excursion | |||
| (re-search-forward bison--word-constituent-re);; SIGERR | |||
| (let ((col (current-column))) | |||
| (cond ((> col (+ 1 bison-rule-enumeration-column)) | |||
| (forward-char -1) | |||
| (just-no-space) | |||
| (indent-to-column bison-rule-enumeration-column)) | |||
| ((< col (+ 1 bison-rule-enumeration-column)) | |||
| (forward-char -1) | |||
| (indent-to-column | |||
| bison-rule-enumeration-column))))))) | |||
| (funcall reset-pt)) | |||
| ((bison--production-alternative-p bol eol section) | |||
| (back-to-indentation);; should put point on "|" | |||
| (if (not (= (current-column) bison-rule-separator-column)) | |||
| (progn | |||
| (just-no-space) | |||
| (indent-to-column bison-rule-separator-column))) | |||
| (forward-char 1) | |||
| (if (following-non-ws-p) | |||
| (save-excursion | |||
| (re-search-forward bison--word-constituent-re);; SIGERR | |||
| (let ((col (current-column))) | |||
| (cond ((> col (+ 1 bison-rule-enumeration-column)) | |||
| (forward-char -1) | |||
| (just-no-space) | |||
| (indent-to-column bison-rule-enumeration-column)) | |||
| ((< col (+ 1 bison-rule-enumeration-column)) | |||
| (forward-char -1) | |||
| (indent-to-column | |||
| bison-rule-enumeration-column)))))) | |||
| (funcall reset-pt)) | |||
| (c-sexp | |||
| (bison--handle-indent-c-sexp | |||
| section bison-rule-enumeration-column bol) | |||
| (funcall reset-pt)) | |||
| ((bison--within-production-body-p section) | |||
| (back-to-indentation) | |||
| (if (not (= (current-column) bison-rule-enumeration-column)) | |||
| (progn | |||
| (just-no-space) | |||
| (indent-to-column | |||
| bison-rule-enumeration-column))) | |||
| (funcall reset-pt)) | |||
| (t | |||
| (let ((cur-ind (current-indentation))) | |||
| (if (eq (save-excursion (search-backward "}" bol t)) | |||
| cur-ind) | |||
| (if (not (= cur-ind bison-rule-enumeration-column)) | |||
| (progn | |||
| (back-to-indentation) | |||
| (just-no-space) | |||
| (indent-to-column bison-rule-enumeration-column) | |||
| (funcall reset-pt))) | |||
| ;; else leave alone | |||
| ))))) | |||
| ((= section bison--c-code-section) | |||
| (c-indent-line)) | |||
| )))) | |||
| ;; *************** electric-functions *************** | |||
| (defun bison-electric-colon (arg) | |||
| "If the colon <:> delineates a production, | |||
| then insert a semicolon on the next line in the BISON-RULE-SEPARATOR-COLUMN, | |||
| put the cursor in the BISON-RULE-ENUMERATION-COLUMN for the beginning | |||
| of the rule | |||
| else just run self-insert-command | |||
| A colon delineates a production by the fact that it is immediately preceded by | |||
| a word(alphanumerics or '_''s), and there is no previous white space. | |||
| " | |||
| (interactive "P") | |||
| (self-insert-command (prefix-numeric-value arg)) | |||
| (if (and bison-electric-colon-v | |||
| (not bison-all-electricity-off)) | |||
| (if (and (= bison--grammar-rules-section (bison--section-p)) | |||
| (bison--production-p) | |||
| (not (bison--within-started-production-p))) | |||
| (progn | |||
| (save-excursion ; put in a closing semicolon | |||
| (newline) | |||
| (indent-to-column bison-rule-separator-column) | |||
| (insert ";")) | |||
| (save-excursion ; remove opening whitespace | |||
| (if (re-search-backward | |||
| "\\s " | |||
| (save-excursion (beginning-of-line) (point)) | |||
| t) | |||
| (just-no-space))) | |||
| (if (not (< (current-column) bison-rule-enumeration-column)) | |||
| (newline)) | |||
| (indent-to-column bison-rule-enumeration-column))))) | |||
| (defun bison-electric-pipe (arg) | |||
| "If the pipe <|> is used as a rule separator within a production, | |||
| then move it into BISON-RULE-SEPARATOR-COLUMN | |||
| indent to BISON-RULE-ENUMERATION-COLUMN on the same line | |||
| else just run self-insert-command | |||
| " | |||
| (interactive "P") | |||
| (if (and bison-electric-pipe-v | |||
| (not bison-all-electricity-off) | |||
| (= bison--grammar-rules-section (bison--section-p)) | |||
| (line-of-whitespace-p) | |||
| ) | |||
| (progn | |||
| (beginning-of-line) | |||
| (just-no-space) | |||
| (indent-to-column bison-rule-separator-column) | |||
| (self-insert-command (prefix-numeric-value arg)) | |||
| (indent-to-column bison-rule-enumeration-column) | |||
| ) | |||
| (self-insert-command (prefix-numeric-value arg)))) | |||
| (defun bison-electric-open-brace (arg) | |||
| "used for the opening brace of a C action definition for production rules, | |||
| if there is only whitespace before \(point\), then put open-brace in | |||
| bison-rule-enumeration-column" | |||
| (interactive "P") | |||
| (if (and bison-electric-open-brace-v | |||
| (not bison-all-electricity-off)) | |||
| (let ((section (bison--section-p))) | |||
| (cond ((and (= section bison--grammar-rules-section) | |||
| (not (bison--within-braced-c-expression-p section)) | |||
| (not (previous-non-ws-p))) | |||
| (if (not (= (current-column) bison-rule-enumeration-column)) | |||
| (progn | |||
| (just-no-space) | |||
| (indent-to-column bison-rule-enumeration-column)))) | |||
| ((and (= section bison--bison-decls-section) | |||
| (not (bison--within-braced-c-expression-p section)) | |||
| (not (previous-non-ws-p))) | |||
| (if (not (= (current-column) 0)) | |||
| (progn | |||
| (just-no-space) | |||
| (indent-to-column 0))))))) | |||
| (self-insert-command (prefix-numeric-value arg))) | |||
| (defun bison-electric-close-brace (arg) | |||
| "If the close-brace \"}\" is used as the c-declarations section closer | |||
| in \"%}\", then make sure the \"%}\" indents to the beginning of the line" | |||
| (interactive "P") | |||
| (self-insert-command (prefix-numeric-value arg)) | |||
| (if (and bison-electric-close-brace-v | |||
| (not bison-all-electricity-off)) | |||
| (cond ((search-backward "%}" (- (point) 2) t) | |||
| (if (= (bison--section-p) bison--c-decls-section) | |||
| (progn | |||
| (just-no-space) | |||
| (forward-char 2)) ; for "%}" | |||
| (forward-char 1))) | |||
| ))) | |||
| (defun bison-electric-semicolon (arg) | |||
| "if the semicolon is used to end a production, then place it in | |||
| bison-rule-separator-column | |||
| a semicolon is deemed to be used for ending a production if it is not found | |||
| within braces | |||
| this is just self-insert-command as i have yet to write the actual | |||
| bison-electric-semicolon function yet | |||
| " | |||
| (interactive "P") | |||
| (self-insert-command (prefix-numeric-value arg))) | |||
| (defun bison-electric-percent (arg) | |||
| "If the percent is a declarer in the bison declaration's section, | |||
| then put it in the 0 column." | |||
| (interactive "P") | |||
| (if (and bison-electric-percent-v | |||
| (not bison-all-electricity-off)) | |||
| (let ((section (bison--section-p))) | |||
| (if (and (= section bison--bison-decls-section) | |||
| (not (bison--within-braced-c-expression-p section)) | |||
| (not (previous-non-ws-p)) | |||
| (not (= (current-column) 0))) | |||
| (just-no-space)))) | |||
| (self-insert-command (prefix-numeric-value arg))) | |||
| (defun bison-electric-less-than (arg) | |||
| "If the less-than is a type declarer opener for tokens in the bison | |||
| declaration section, then put it in the bison-decl-type-column column." | |||
| (interactive "P") | |||
| (if (and bison-electric-less-than-v | |||
| (not bison-all-electricity-off)) | |||
| (if (and (= (bison--section-p) bison--bison-decls-section) | |||
| (bison--bison-decl-opener-p | |||
| (save-excursion (beginning-of-line) (point)) | |||
| (point))) | |||
| (progn | |||
| (just-no-space) | |||
| (indent-to-column bison-decl-type-column)))) | |||
| (self-insert-command (prefix-numeric-value arg))) | |||
| (defun bison-electric-greater-than (arg) | |||
| "If the greater-than is a type declarer closer for tokens in the bison | |||
| declaration section, then indent to bison-decl-token-column." | |||
| (interactive "P") | |||
| (self-insert-command (prefix-numeric-value arg)) | |||
| (if (and bison-electric-greater-than-v | |||
| (not bison-all-electricity-off)) | |||
| (let ((current-pt (point)) | |||
| (bol (save-excursion (beginning-of-line) (point)))) | |||
| (if (and (= (bison--section-p) bison--bison-decls-section) | |||
| (bison--bison-decl-opener-p bol (point))) | |||
| (if (search-backward "<" bol t) | |||
| (if (re-search-forward | |||
| (concat "<" bison--word-constituent-re "+>") | |||
| current-pt t) | |||
| (if (not (following-non-ws-p)) | |||
| (progn | |||
| (just-no-space) | |||
| (indent-to-column bison-decl-token-column))))))))) | |||
| (define-derived-mode jison-mode bison-mode | |||
| "Major mode for editing jison files.") | |||
| (provide 'bison-mode) | |||
| (provide 'jison-mode) | |||
| ;;; bison-mode.el ends here | |||
| @ -0,0 +1,63 @@ | |||
| ;;; elixir-deprecated.el --- Functionality to display deprecated messages | |||
| ;; Copyright 2011-2014 secondplanet | |||
| ;; 2013-2014 Matt DeBoard, Samuel Tonini, Andreas Fuchs | |||
| ;; Authors: Humza Yaqoob, | |||
| ;; Andreas Fuchs <asf@boinkor.net>, | |||
| ;; Matt DeBoard | |||
| ;; 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 display deprecated messages | |||
| ;;; Code: | |||
| (defface elixir-deprecated--warning-face | |||
| '((t (:inherit font-lock-variable-name-face :bold t :foreground "red"))) | |||
| "" | |||
| :group 'elixir-deprecated) | |||
| (defface elixir-deprecated--function-face | |||
| '((t (:inherit font-lock-variable-name-face :bold t :foreground "green"))) | |||
| "" | |||
| :group 'elixir-deprecated) | |||
| (defun elixir-deprecated--warning (function-name message) | |||
| (let ((buffer (get-buffer "*Warnings*"))) | |||
| (when buffer | |||
| (kill-buffer buffer)) | |||
| (display-warning :deprecated | |||
| (concat "\n\n" | |||
| (propertize "DEPRECATION WARNING: " | |||
| 'face 'elixir-deprecated--warning-face) | |||
| (propertize (format "[ %s ]\n\n" function-name) | |||
| 'face 'elixir-deprecated--function-face) | |||
| message)) :warning)) | |||
| ;; DEPRECATED MESSAGES FOR RELEASE 3.0.0 | |||
| (defvar elixir-deprecated--alchemist-message "This function will be removed in version 3.0.0.\n | |||
| Please use the package *alchemist.el* for compilation functionality.\n | |||
| Alchemist: http://www.github.com/tonini/alchemist.el") | |||
| (defun elixir-deprecated-use-alchemist (function-name) | |||
| (elixir-deprecated--warning function-name | |||
| elixir-deprecated--alchemist-message)) | |||
| (provide 'elixir-deprecated) | |||
| @ -0,0 +1,76 @@ | |||
| ;;; elixir-mode-autoloads.el --- automatically extracted autoloads | |||
| ;; | |||
| ;;; Code: | |||
| (add-to-list 'load-path (or (file-name-directory #$) (car load-path))) | |||
| ;;;### (autoloads nil "elixir-mode" "elixir-mode.el" (21721 24856 | |||
| ;;;;;; 0 0)) | |||
| ;;; Generated autoloads from elixir-mode.el | |||
| (autoload 'elixir-mode-iex "elixir-mode" "\ | |||
| Elixir mode interactive REPL. | |||
| Optional argument ARGS-P . | |||
| \(fn &optional ARGS-P)" t nil) | |||
| (autoload 'elixir-mode-open-modegithub "elixir-mode" "\ | |||
| Elixir mode open GitHub page. | |||
| \(fn)" t nil) | |||
| (autoload 'elixir-mode-open-elixir-home "elixir-mode" "\ | |||
| Elixir mode go to language home. | |||
| \(fn)" t nil) | |||
| (autoload 'elixir-mode-open-docs-master "elixir-mode" "\ | |||
| Elixir mode go to master documentation. | |||
| \(fn)" t nil) | |||
| (autoload 'elixir-mode-open-docs-stable "elixir-mode" "\ | |||
| Elixir mode go to stable documentation. | |||
| \(fn)" t nil) | |||
| (autoload 'elixir-mode-version "elixir-mode" "\ | |||
| Get the Elixir-Mode 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. | |||
| \(fn &optional SHOW-VERSION)" t nil) | |||
| (autoload 'elixir-mode "elixir-mode" "\ | |||
| Major mode for editing Elixir code. | |||
| \\{elixir-mode-map} | |||
| \(fn)" t nil) | |||
| (add-to-list 'auto-mode-alist '("\\.elixir\\'" . elixir-mode)) | |||
| (add-to-list 'auto-mode-alist '("\\.ex\\'" . elixir-mode)) | |||
| (add-to-list 'auto-mode-alist '("\\.exs\\'" . elixir-mode)) | |||
| ;;;*** | |||
| ;;;### (autoloads nil nil ("elixir-deprecated.el" "elixir-mode-pkg.el" | |||
| ;;;;;; "elixir-smie.el") (21721 24856 873809 0)) | |||
| ;;;*** | |||
| ;; Local Variables: | |||
| ;; version-control: never | |||
| ;; no-byte-compile: t | |||
| ;; no-update-autoloads: t | |||
| ;; End: | |||
| ;;; elixir-mode-autoloads.el ends here | |||
| @ -0,0 +1,5 @@ | |||
| (define-package "elixir-mode" "20150103.439" "Major mode for editing Elixir files" 'nil :url "https://github.com/elixir-lang/emacs-elixir" :keywords | |||
| '("languages" "elixir")) | |||
| ;; Local Variables: | |||
| ;; no-byte-compile: t | |||
| ;; End: | |||
| @ -0,0 +1,608 @@ | |||
| ;;; elixir-mode.el --- Major mode for editing Elixir files | |||
| ;; Copyright 2011-2015 secondplanet | |||
| ;; 2013-2015 Samuel Tonini, Matt DeBoard, Andreas Fuchs | |||
| ;; Authors: Humza Yaqoob, | |||
| ;; Andreas Fuchs <asf@boinkor.net>, | |||
| ;; Matt DeBoard | |||
| ;; Samuel Tonini <tonini.samuel@gmail.com> | |||
| ;; URL: https://github.com/elixir-lang/emacs-elixir | |||
| ;; Created: Mon Nov 7 2011 | |||
| ;; Keywords: languages elixir | |||
| ;; Version: 2.3.0-cvs | |||
| ;; This file is not a 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 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 this program; if not, write to the Free Software | |||
| ;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |||
| ;;; Commentary: | |||
| ;; Provides font-locking, indentation and navigation support | |||
| ;; for the Elixir programming language. | |||
| ;;; Code: | |||
| (require 'comint) ; for interactive REPL | |||
| (require 'easymenu) ; for menubar features | |||
| (require 'elixir-smie) ; syntax and indentation support | |||
| (require 'elixir-deprecated) ; deprecated messages | |||
| (defgroup elixir-mode nil | |||
| "Provides font-locking, indentation and navigation support | |||
| for the Elixir programming language." | |||
| :prefix "elixir-mode-" | |||
| :group 'applications | |||
| :link '(url-link :tag "Github" "https://github.com/elixir-lang/emacs-elixir") | |||
| :link '(emacs-commentary-link :tag "Commentary" "elixir-mode")) | |||
| (defvar elixir-mqode--website-url | |||
| "http://elixir-lang.org") | |||
| (defvar elixir-mode-hook nil) | |||
| (defvar elixir-mode-map | |||
| (let ((map (make-sparse-keymap))) | |||
| (define-key map (kbd "C-c ,r") 'elixir-mode-eval-on-region) | |||
| (define-key map (kbd "C-c ,c") 'elixir-mode-eval-on-current-line) | |||
| (define-key map (kbd "C-c ,b") 'elixir-mode-eval-on-current-buffer) | |||
| (define-key map (kbd "C-c ,a") 'elixir-mode-string-to-quoted-on-region) | |||
| (define-key map (kbd "C-c ,l") 'elixir-mode-string-to-quoted-on-current-line) | |||
| map) | |||
| "Keymap used in `elixir-mode'.") | |||
| (defvar elixir-imenu-generic-expression | |||
| '(("Modules" "^\\s-*defmodule[ \n\t]+\\([A-Z][A-Za-z0-9._]+\\)\\s-+do.*$" 1) | |||
| ("Public Functions" "^\\s-*def[ \n\t]+\\([a-z0-9_]+\\)\\(([^)]*)\\)*[ \t\n]+do.*" 1) | |||
| ("Private Functions" "^\\s-*defp[ \n\t]+\\([a-z0-9_]+\\)\\(([^)]*)\\)*[ \t\n]+do.*" 1) | |||
| ("Public Macros" "^\\s-*defmacro[ \n\t]+\\([a-z0-9_]+\\)\\(([^)]*)\\)*[ \t\n]+do.*" 1) | |||
| ("Private Macros" "^\\s-*defmacrop[ \n\t]+\\([a-z0-9_]+\\)\\(([^)]*)\\)*[ \t\n]+do.*" 1) | |||
| ("Delegates" "^\\s-*defdelegate[ \n\t]+\\([a-z0-9_]+\\)\\(([^)]*)\\)*[ \t\n]+do.*" 1) | |||
| ("Overridables" "^\\s-*defoverridable[ \n\t]+\\([a-z0-9_]+\\)\\(([^)]*)\\)*[ \t\n]+do.*" 1) | |||
| ("Tests" "^\\s-*test[ \t\n]+\"?\\(:?[a-z0-9_@+() \t-]+\\)\"?[ \t\n]+do.*" 1)) | |||
| "Imenu pattern for `elixir-mode'.") | |||
| (defgroup elixir nil | |||
| "Elixir major mode." | |||
| :group 'languages) | |||
| (defcustom elixir-compiler-command "elixirc" | |||
| "Elixir mode command to compile code. Must be in your path." | |||
| :type 'string | |||
| :group 'elixir) | |||
| (defcustom elixir-mode-command "elixir" | |||
| "The command for elixir." | |||
| :type 'string | |||
| :group 'elixir) | |||
| (defcustom elixir-iex-command "iex" | |||
| "Elixir mode command for interactive REPL. Must be in your path." | |||
| :type 'string | |||
| :group 'elixir) | |||
| (defcustom elixir-mode-cygwin-paths t | |||
| "Elixir mode use Cygwin style paths on Windows operating systems." | |||
| :type 'boolean | |||
| :group 'elixir) | |||
| (defcustom elixir-mode-cygwin-prefix "/cygdrive/C" | |||
| "Elixir mode Cygwin prefix." | |||
| :type 'string | |||
| :group 'elixir) | |||
| (defvar elixir-mode--eval-filename "elixir-mode-tmp-eval-file.exs") | |||
| (defvar elixir-quoted--buffer-name "*elixir-quoted*") | |||
| (defvar elixir-basic-offset 2) | |||
| (defvar elixir-key-label-offset 0) | |||
| (defvar elixir-match-label-offset 2) | |||
| (defvar elixir-operator-face 'elixir-operator-face) | |||
| (defface elixir-operator-face | |||
| '((((class color) (min-colors 88) (background light)) | |||
| :foreground "darkred") | |||
| (((class color) (background dark)) | |||
| (:foreground "lemonchiffon1")) | |||
| (t nil)) | |||
| "For use with operators." | |||
| :group 'font-lock-faces) | |||
| (defvar elixir-negation-face 'elixir-negation-face) | |||
| (defface elixir-negation-face | |||
| '((((class color) (min-colors 88) (background light)) | |||
| :foreground "#ff4500") | |||
| (((class color) (background dark)) | |||
| (:foreground "#ff4500")) | |||
| (t nil)) | |||
| "For use with standalone \"?\" to indicate code point." | |||
| :group 'font-lock-faces) | |||
| (defvar elixir-attribute-face 'elixir-attribute-face) | |||
| (defface elixir-attribute-face | |||
| '((((class color) (min-colors 88) (background light)) | |||
| :foreground "MediumPurple4") | |||
| (((class color) (background dark)) | |||
| (:foreground "thistle")) | |||
| (t nil)) | |||
| "For use with module attribute tokens." | |||
| :group 'font-lock-faces) | |||
| (defvar elixir-atom-face 'elixir-atom-face) | |||
| (defface elixir-atom-face | |||
| '((((class color) (min-colors 88) (background light)) | |||
| :foreground "RoyalBlue4") | |||
| (((class color) (background dark)) | |||
| (:foreground "light sky blue")) | |||
| (t nil)) | |||
| "For use with atoms & map keys." | |||
| :group 'font-lock-faces) | |||
| (defun elixir-syntax-propertize-interpolation () | |||
| (let* ((beg (match-beginning 0)) | |||
| (context (save-excursion (save-match-data (syntax-ppss beg))))) | |||
| (put-text-property beg (1+ beg) 'elixir-interpolation | |||
| (cons (nth 3 context) (match-data))))) | |||
| (defun elixir-syntax-propertize-function (start end) | |||
| (let ((case-fold-search nil)) | |||
| (goto-char start) | |||
| (remove-text-properties start end '(elixir-interpolation)) | |||
| (funcall | |||
| (syntax-propertize-rules | |||
| ((rx (group "#{" (0+ (not (any "}"))) "}")) | |||
| (0 (ignore (elixir-syntax-propertize-interpolation))))) | |||
| start end))) | |||
| (defun elixir-match-interpolation (limit) | |||
| (let ((pos (next-single-char-property-change (point) 'elixir-interpolation | |||
| nil limit))) | |||
| (when (and pos (> pos (point))) | |||
| (goto-char pos) | |||
| (let ((value (get-text-property pos 'elixir-interpolation))) | |||
| (if (eq (car value) ?\") | |||
| (progn | |||
| (set-match-data (cdr value)) | |||
| t) | |||
| (elixir-match-interpolation limit)))))) | |||
| (eval-when-compile | |||
| (defconst elixir-rx-constituents | |||
| `( | |||
| (atoms . ,(rx ":" | |||
| (or | |||
| (one-or-more (any "a-z" "A-Z" "_" "\"" "'")) | |||
| (and "\"" (one-or-more (not (any "\""))) "\"") | |||
| (and "'" (one-or-more (not (any "'"))) "'")))) | |||
| (builtin . ,(rx symbol-start | |||
| (or "case" "cond" "for" "if" "unless" "try" "receive" | |||
| "raise" "quote" "unquote" "unquote_splicing" "throw" | |||
| "super") | |||
| symbol-end)) | |||
| (builtin-declaration . ,(rx symbol-start | |||
| (or "def" "defp" "defmodule" "defprotocol" | |||
| "defmacro" "defmacrop" "defdelegate" | |||
| "defexception" "defstruct" "defimpl" | |||
| "defcallback") | |||
| symbol-end)) | |||
| (builtin-modules . ,(rx symbol-start | |||
| (or "Agent" "Application" "Atom" "Base" | |||
| "Behaviour" "Bitwise" "Builtin" "Code" "Dict" | |||
| "EEx" "Elixir" "Enum" "ExUnit" "Exception" | |||
| "File" "File.Stat" "File.Stream" "Float" | |||
| "Function" "GenEvent" "GenServer" "GenTCP" | |||
| "HashDict" "HashSet" "IO" "IO.ANSI" | |||
| "IO.Stream" "Inspect.Algebra" "Inspect.Opts" | |||
| "Integer" "Kernel" "Kernel.ParallelCompiler" | |||
| "Kernel.ParallelRequire" "Kernel.SpecialForms" | |||
| "Kernel.Typespec" "Keyword" "List" "Macro" | |||
| "Macro.Env" "Map" "Math" "Module" "Node" | |||
| "OptionParser" "OrdDict" "Path" "Port" | |||
| "Process" "Protocol" "Range" "Record" "Regex" | |||
| "Set" "Stream" "String" "StringIO" | |||
| "Supervisor" "Supervisor.Spec" "System" "Task" | |||
| "Task.Supervisor" "Tuple" "URI" | |||
| "UnboundMethod" "Version") | |||
| symbol-end)) | |||
| (builtin-namespace . ,(rx symbol-start | |||
| (or "import" "require" "use" "alias") | |||
| symbol-end)) | |||
| ;; Set aside code point syntax for `elixir-negation-face'. | |||
| (code-point . ,(rx symbol-start | |||
| "?" | |||
| anything | |||
| symbol-end)) | |||
| (function-declaration . ,(rx symbol-start | |||
| (or "def" "defp") | |||
| symbol-end)) | |||
| ;; Match `@doc' or `@moduledoc' syntax, with or without triple quotes. | |||
| (heredocs . ,(rx symbol-start | |||
| (or "@doc" "@moduledoc" "~s") | |||
| symbol-end)) | |||
| ;; The first character of an identifier must be a letter or an underscore. | |||
| ;; After that, they may contain any alphanumeric character + underscore. | |||
| ;; Additionally, the final character may be either `?' or `!'. | |||
| (identifiers . ,(rx (one-or-more (any "A-Z" "a-z" "_")) | |||
| (zero-or-more (any "A-Z" "a-z" "0-9" "_")) | |||
| (optional (or "?" "!")))) | |||
| (keyword . ,(rx symbol-start | |||
| (or "fn" "do" "end" "after" "else" "rescue" "catch") | |||
| symbol-end)) | |||
| (keyword-operator . ,(rx symbol-start | |||
| (or "not" "and" "or" "when" "in") | |||
| symbol-end)) | |||
| ;; Module and submodule names start with upper case letter. This | |||
| ;; can then be followed by any combination of alphanumeric chars. | |||
| ;; In turn, this can be followed by a `.' which begins the notation of | |||
| ;; a submodule, which follows the same naming pattern of the module. | |||
| ;; Finally, like other identifiers, it can be terminated with either `?' | |||
| ;; or `!'. | |||
| (module-names . ,(rx symbol-start | |||
| (one-or-more (any "A-Z")) | |||
| (zero-or-more (any "A-Z" "a-z" "_" "0-9")) | |||
| (zero-or-more | |||
| (and "." | |||
| (one-or-more (any "A-Z" "_")) | |||
| (zero-or-more (any "A-Z" "a-z" "_" "0-9")))) | |||
| (optional (or "!" "?")) | |||
| symbol-end)) | |||
| (operators1 . ,(rx symbol-start | |||
| (or "<" ">" "+" "-" "*" "/" "!" "^" "&") | |||
| symbol-end)) | |||
| (operators2 . ,(rx symbol-start | |||
| (or | |||
| "==" "!=" "<=" ">=" "&&" "||" "<>" "++" "--" "|>" "=~" | |||
| "->" "<-" "|" "." "=") | |||
| symbol-end)) | |||
| (operators3 . ,(rx symbol-start | |||
| (or "<<<" ">>>" "|||" "&&&" "^^^" "~~~" "===" "!==") | |||
| symbol-end)) | |||
| (pseudo-var . ,(rx symbol-start | |||
| (or "_" "__MODULE__" "__DIR__" "__ENV__" "__CALLER__" | |||
| "__block__" "__aliases__") | |||
| symbol-end)) | |||
| (punctuation . ,(rx symbol-start | |||
| (or "\\" "<<" ">>" "=>" "(" ")" ":" ";" "" "[" "]") | |||
| symbol-end)) | |||
| (sigils . ,(rx "~" (or "B" "C" "R" "S" "b" "c" "r" "s" "w"))))) | |||
| (defmacro elixir-rx (&rest sexps) | |||
| (let ((rx-constituents (append elixir-rx-constituents rx-constituents))) | |||
| (cond ((null sexps) | |||
| (error "No regexp")) | |||
| ((cdr sexps) | |||
| (rx-to-string `(and ,@sexps) t)) | |||
| (t | |||
| (rx-to-string (car sexps) t)))))) | |||
| (defconst elixir-font-lock-keywords | |||
| `( | |||
| ;; String interpolation | |||
| (elixir-match-interpolation 0 font-lock-variable-name-face t) | |||
| ;; Module-defining & namespace builtins | |||
| (,(elixir-rx (or builtin-declaration builtin-namespace) | |||
| space | |||
| (group module-names)) | |||
| 1 font-lock-type-face) | |||
| ;; Module attributes | |||
| (,(elixir-rx (group (or heredocs | |||
| (and "@" (1+ identifiers))))) | |||
| 1 elixir-attribute-face) | |||
| ;; Keywords | |||
| (,(elixir-rx (group (or builtin builtin-declaration builtin-namespace | |||
| keyword keyword-operator))) | |||
| 1 font-lock-keyword-face) | |||
| ;; Function names, i.e. `def foo do'. | |||
| (,(elixir-rx (group function-declaration) | |||
| space | |||
| (group identifiers)) | |||
| 2 font-lock-function-name-face) | |||
| ;; Variable definitions | |||
| (,(elixir-rx (group identifiers) | |||
| (one-or-more space) | |||
| "=" | |||
| (or (one-or-more space) | |||
| (one-or-more "\n"))) | |||
| 1 font-lock-variable-name-face) | |||
| ;; Sigils | |||
| (,(elixir-rx (group sigils)) | |||
| 1 font-lock-builtin-face) | |||
| ;; Regex patterns. Elixir has support for eight different regex delimiters. | |||
| ;; This isn't a very DRY approach here but it gets the job done. | |||
| (,(elixir-rx "~r" | |||
| (and "/" (group (one-or-more (not (any "/")))) "/")) | |||
| 1 font-lock-string-face) | |||
| (,(elixir-rx "~r" | |||
| (and "[" (group (one-or-more (not (any "]")))) "]")) | |||
| 1 font-lock-string-face) | |||
| (,(elixir-rx "~r" | |||
| (and "{" (group (one-or-more (not (any "}")))) "}")) | |||
| 1 font-lock-string-face) | |||
| (,(elixir-rx "~r" | |||
| (and "(" (group (one-or-more (not (any ")")))) ")")) | |||
| 1 font-lock-string-face) | |||
| (,(elixir-rx "~r" | |||
| (and "|" (group (one-or-more (not (any "|")))) "|")) | |||
| 1 font-lock-string-face) | |||
| (,(elixir-rx "~r" | |||
| (and "\"" (group (one-or-more (not (any "\"")))) "\"")) | |||
| 1 font-lock-string-face) | |||
| (,(elixir-rx "~r" | |||
| (and "'" (group (one-or-more (not (any "'")))) "'")) | |||
| 1 font-lock-string-face) | |||
| (,(elixir-rx "~r" | |||
| (and "<" (group (one-or-more (not (any ">")))) ">")) | |||
| 1 font-lock-string-face) | |||
| ;; Atoms and singleton-like words like true/false/nil. | |||
| (,(elixir-rx (group atoms)) | |||
| 1 elixir-atom-face) | |||
| ;; Map keys | |||
| (,(elixir-rx (group (and (one-or-more identifiers) ":"))) | |||
| 1 elixir-atom-face) | |||
| ;; Built-in modules and pseudovariables | |||
| (,(elixir-rx (group (or builtin-modules pseudo-var))) | |||
| 1 font-lock-constant-face) | |||
| ;; Code points | |||
| (,(elixir-rx (group code-point)) | |||
| 1 elixir-negation-face))) | |||
| (defun elixir-mode-cygwin-path (expanded-file-name) | |||
| "Elixir mode get Cygwin absolute path name. | |||
| Argument EXPANDED-FILE-NAME ." | |||
| (replace-regexp-in-string "^[a-zA-Z]:" elixir-mode-cygwin-prefix expanded-file-name t)) | |||
| (defun elixir-mode-universal-path (file-name) | |||
| "Elixir mode multi-OS path handler. | |||
| Argument FILE-NAME ." | |||
| (let ((full-file-name (expand-file-name file-name))) | |||
| (if (and (equal system-type 'windows-nt) | |||
| elixir-mode-cygwin-paths) | |||
| (elixir-mode-cygwin-path full-file-name) | |||
| full-file-name))) | |||
| (defun elixir-mode-command-compile (file-name) | |||
| "Elixir mode command to compile a file. | |||
| Argument FILE-NAME ." | |||
| (let ((full-file-name (elixir-mode-universal-path file-name))) | |||
| (mapconcat 'identity (append (list elixir-compiler-command) (list full-file-name)) " "))) | |||
| (defun elixir-mode-compiled-file-name (&optional filename) | |||
| "Elixir mode compiled FILENAME." | |||
| (concat (file-name-sans-extension (or filename (buffer-file-name))) ".beam")) | |||
| (defun elixir-mode-compile-file () | |||
| "Elixir mode compile and save current file." | |||
| (interactive) | |||
| (elixir-deprecated-use-alchemist "elixir-mode-compile-file") | |||
| (let ((compiler-output (shell-command-to-string (elixir-mode-command-compile (buffer-file-name))))) | |||
| (when (string= compiler-output "") | |||
| (message "Compiled and saved as %s" (elixir-mode-compiled-file-name))))) | |||
| (defun elixir-quoted--initialize-buffer (quoted) | |||
| (pop-to-buffer elixir-quoted--buffer-name) | |||
| (setq buffer-undo-list nil) ; Get rid of undo information from | |||
| ; previous expansions | |||
| (let ((inhibit-read-only t) | |||
| (buffer-undo-list t)) ; Ignore undo information | |||
| (erase-buffer) | |||
| (insert quoted) | |||
| (goto-char (point-min)) | |||
| (elixir-mode) | |||
| (elixir-quoted-minor-mode 1))) | |||
| ;;;###autoload | |||
| (defun elixir-mode-iex (&optional args-p) | |||
| "Elixir mode interactive REPL. | |||
| Optional argument ARGS-P ." | |||
| (interactive "P") | |||
| (let ((switches (if (equal args-p nil) | |||
| '() | |||
| (split-string (read-string "Additional args: "))))) | |||
| (unless (comint-check-proc "*IEX*") | |||
| (set-buffer | |||
| (apply 'make-comint "IEX" | |||
| elixir-iex-command nil switches)))) | |||
| (pop-to-buffer "*IEX*") | |||
| (elixir-deprecated-use-alchemist "elixir-mode-iex")) | |||
| ;;;###autoload | |||
| (defun elixir-mode-open-modegithub () | |||
| "Elixir mode open GitHub page." | |||
| (interactive) | |||
| (browse-url "https://github.com/elixir-lang/emacs-elixir")) | |||
| ;;;###autoload | |||
| (defun elixir-mode-open-elixir-home () | |||
| "Elixir mode go to language home." | |||
| (interactive) | |||
| (browse-url elixir-mode--website-url)) | |||
| ;;;###autoload | |||
| (defun elixir-mode-open-docs-master () | |||
| "Elixir mode go to master documentation." | |||
| (interactive) | |||
| (browse-url (concat elixir-mode--website-url "/docs/master/elixir"))) | |||
| ;;;###autoload | |||
| (defun elixir-mode-open-docs-stable () | |||
| "Elixir mode go to stable documentation." | |||
| (interactive) | |||
| (browse-url (concat elixir-mode--website-url "/docs/stable/elixir"))) | |||
| ;;;###autoload | |||
| (defun elixir-mode-version (&optional show-version) | |||
| "Get the Elixir-Mode 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 'elixir-mode))) | |||
| (when show-version | |||
| (message "Elixir-Mode version: %s" version)) | |||
| version)) | |||
| (defun elixir-mode--code-eval-string-command (file) | |||
| (format "%s -e 'IO.puts inspect(elem(Code.eval_string(File.read!(\"%s\")), 0))'" | |||
| elixir-mode-command | |||
| file)) | |||
| (defun elixir-mode--code-string-to-quoted-command (file) | |||
| (format "%s -e 'IO.puts inspect(elem(Code.string_to_quoted(File.read!(\"%s\")), 1), pretty: true)'" | |||
| elixir-mode-command | |||
| file)) | |||
| (defun elixir-mode--execute-elixir-with-code-eval-string (string) | |||
| (with-temp-file elixir-mode--eval-filename | |||
| (insert string)) | |||
| (let ((output (shell-command-to-string (elixir-mode--code-eval-string-command elixir-mode--eval-filename)))) | |||
| (delete-file elixir-mode--eval-filename) | |||
| output)) | |||
| (defun elixir-mode--execute-elixir-with-code-string-to-quoted (string) | |||
| (with-temp-file elixir-mode--eval-filename | |||
| (insert string)) | |||
| (let ((output (shell-command-to-string (elixir-mode--code-string-to-quoted-command elixir-mode--eval-filename)))) | |||
| (delete-file elixir-mode--eval-filename) | |||
| output)) | |||
| (defun elixir-mode--eval-string (string) | |||
| (let ((output (elixir-mode--execute-elixir-with-code-eval-string string))) | |||
| (message output))) | |||
| (defun elixir-mode--string-to-quoted (string) | |||
| (let* ((output (elixir-mode--execute-elixir-with-code-string-to-quoted string))) | |||
| (elixir-quoted--initialize-buffer output))) | |||
| (defun elixir-mode-eval-on-region (beg end) | |||
| "Evaluate the Elixir code on the marked region. | |||
| Argument BEG Start of the region. | |||
| Argument END End of the region." | |||
| (interactive (list (point) (mark))) | |||
| (elixir-deprecated-use-alchemist "elixir-mode-eval-on-region") | |||
| (unless (and beg end) | |||
| (error "The mark is not set now, so there is no region")) | |||
| (let* ((region (buffer-substring-no-properties beg end))) | |||
| (elixir-mode--eval-string region))) | |||
| (defun elixir-mode-eval-on-current-line () | |||
| "Evaluate the Elixir code on the current line." | |||
| (interactive) | |||
| (elixir-deprecated-use-alchemist "elixir-mode-eval-on-current-line") | |||
| (let ((current-line (thing-at-point 'line))) | |||
| (elixir-mode--eval-string current-line))) | |||
| (defun elixir-mode-eval-on-current-buffer () | |||
| "Evaluate the Elixir code on the current buffer." | |||
| (interactive) | |||
| (elixir-deprecated-use-alchemist "elixir-mode-eval-on-current-buffer") | |||
| (let ((current-buffer (buffer-substring-no-properties (point-max) (point-min)))) | |||
| (elixir-mode--eval-string current-buffer))) | |||
| (defun elixir-mode-string-to-quoted-on-region (beg end) | |||
| "Get the representation of the expression on the marked region. | |||
| Argument BEG Start of the region. | |||
| Argument END End of the region." | |||
| (interactive (list (point) (mark))) | |||
| (elixir-deprecated-use-alchemist "elixir-mode-string-to-quoted-on-region") | |||
| (unless (and beg end) | |||
| (error "The mark is not set now, so there is no region")) | |||
| (let ((region (buffer-substring-no-properties beg end))) | |||
| (elixir-mode--string-to-quoted region))) | |||
| (defun elixir-mode-string-to-quoted-on-current-line () | |||
| "Get the representation of the expression on the current line." | |||
| (interactive) | |||
| (elixir-deprecated-use-alchemist "elixir-mode-string-to-quoted-on-current-line") | |||
| (let ((current-line (thing-at-point 'line))) | |||
| (elixir-mode--string-to-quoted current-line))) | |||
| (easy-menu-define elixir-mode-menu elixir-mode-map | |||
| "Elixir mode menu." | |||
| '("Elixir" | |||
| ["Indent line" smie-indent-line] | |||
| ["Compile file" elixir-mode-compile-file] | |||
| ["IEX" elixir-mode-iex] | |||
| "---" | |||
| ["elixir-mode on GitHub" elixir-mode-open-modegithub] | |||
| ["Elixir homepage" elixir-mode-open-elixirhome] | |||
| ["About" elixir-mode-version] | |||
| )) | |||
| ;;;###autoload | |||
| (define-derived-mode elixir-mode prog-mode "Elixir" | |||
| "Major mode for editing Elixir code. | |||
| \\{elixir-mode-map}" | |||
| (set (make-local-variable 'font-lock-defaults) | |||
| '(elixir-font-lock-keywords)) | |||
| (set (make-local-variable 'comment-start) "# ") | |||
| (set (make-local-variable 'comment-end) "") | |||
| (set (make-local-variable 'comment-start-skip) "#+ *") | |||
| (set (make-local-variable 'comment-use-syntax) t) | |||
| (set (make-local-variable 'tab-width) elixir-basic-offset) | |||
| (set (make-local-variable 'syntax-propertize-function) | |||
| #'elixir-syntax-propertize-function) | |||
| (set (make-local-variable 'imenu-generic-expression) | |||
| elixir-imenu-generic-expression) | |||
| (smie-setup elixir-smie-grammar 'verbose-elixir-smie-rules | |||
| :forward-token 'elixir-smie-forward-token | |||
| :backward-token 'elixir-smie-backward-token)) | |||
| (define-minor-mode elixir-cos-mode | |||
| "Elixir mode toggle compile on save." | |||
| :group 'elixir-cos :lighter " CoS" | |||
| (cond | |||
| (elixir-cos-mode | |||
| (add-hook 'after-save-hook 'elixir-mode-compile-file nil t)) | |||
| (t | |||
| (remove-hook 'after-save-hook 'elixir-mode-compile-file t)))) | |||
| (define-minor-mode elixir-quoted-minor-mode | |||
| "Minor mode for displaying elixir quoted expressions" | |||
| :group 'elixir-quoted :lighter " quoted" | |||
| :keymap '(("q" . quit-window)) | |||
| (setq buffer-read-only t)) | |||
| ;; Invoke elixir-mode when appropriate | |||
| ;;;###autoload | |||
| (progn | |||
| (add-to-list 'auto-mode-alist '("\\.elixir\\'" . elixir-mode)) | |||
| (add-to-list 'auto-mode-alist '("\\.ex\\'" . elixir-mode)) | |||
| (add-to-list 'auto-mode-alist '("\\.exs\\'" . elixir-mode))) | |||
| (provide 'elixir-mode) | |||
| ;;; elixir-mode.el ends here | |||
| @ -0,0 +1,348 @@ | |||
| ;;; elixir-smie.el --- Structural syntax support for elixir-mode | |||
| ;; Copyright 2011-2015 secondplanet | |||
| ;; 2013-2015 Samuel Tonini, Matt DeBoard, Andreas Fuchs | |||
| ;; This file is not a 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 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 this program; if not, write to the Free Software | |||
| ;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |||
| ;;; Commentary: | |||
| ;; Structural syntax support for elixir-mode | |||
| ;;; Code: | |||
| (require 'smie) | |||
| ;; HACK: Patch for Emacs 24.3 smie that fix | |||
| ;; https://github.com/elixir-lang/emacs-elixir/issues/107. | |||
| ;; | |||
| ;; defadvice is used to change the behavior only for elixir-mode. | |||
| ;; Definition of advice is a definition of corresponding function | |||
| ;; in Emacs 24.4. | |||
| (when (and (= 24 emacs-major-version) | |||
| (= 3 emacs-minor-version)) | |||
| (defadvice smie-rule-parent (around elixir-mode-patch activate) | |||
| (if (not (eq major-mode 'elixir-mode)) | |||
| (progn ad-do-it) | |||
| (setq ad-return-value | |||
| (save-excursion | |||
| (goto-char (cadr (smie-indent--parent))) | |||
| (cons 'column | |||
| (+ (or offset 0) | |||
| (smie-indent-virtual))))))) | |||
| (defadvice smie-indent-comment (around elixir-mode-patch activate) | |||
| (if (not (eq major-mode 'elixir-mode)) | |||
| (progn ad-do-it) | |||
| (setq ad-return-value | |||
| (and (smie-indent--bolp) | |||
| (let ((pos (point))) | |||
| (save-excursion | |||
| (beginning-of-line) | |||
| (and (re-search-forward comment-start-skip (line-end-position) t) | |||
| (eq pos (or (match-end 1) (match-beginning 0)))))) | |||
| (save-excursion | |||
| (forward-comment (point-max)) | |||
| (skip-chars-forward " \t\r\n") | |||
| (unless | |||
| (save-excursion | |||
| (let ((next (funcall smie-forward-token-function))) | |||
| (or (if (zerop (length next)) | |||
| (or (eobp) (eq (car (syntax-after (point))) 5))) | |||
| (rassoc next smie-closer-alist)))) | |||
| (smie-indent-calculate)))))))) | |||
| ;; FIXME: This is me being lazy. CL is a compile-time dep only. | |||
| ;; (But for now, there is no real file-compilation plot, so let's | |||
| ;; scrape by with the runtime dep.) | |||
| (require 'cl) | |||
| (defvar elixir-smie-verbose-p nil | |||
| "Emit context information about the current syntax state.") | |||
| (defvar elixir-mode-syntax-table | |||
| (let ((table (make-syntax-table))) | |||
| ;; Note that ?_ might be better as class "_", but either seems to | |||
| ;; work: | |||
| (modify-syntax-entry ?_ "_" table) | |||
| (modify-syntax-entry ?? "w" table) | |||
| (modify-syntax-entry ?~ "w" table) | |||
| (modify-syntax-entry ?! "_" table) | |||
| (modify-syntax-entry ?' "\"'" table) | |||
| (modify-syntax-entry ?\" "\"\"" table) | |||
| (modify-syntax-entry ?# "<" table) | |||
| (modify-syntax-entry ?\n ">" table) | |||
| (modify-syntax-entry ?\( "()" table) | |||
| (modify-syntax-entry ?\) ")(" table) | |||
| (modify-syntax-entry ?\{ "(}" table) | |||
| (modify-syntax-entry ?\} "){" table) | |||
| (modify-syntax-entry ?\[ "(]" table) | |||
| (modify-syntax-entry ?\] ")[" table) | |||
| (modify-syntax-entry ?: "_" table) | |||
| (modify-syntax-entry ?@ "_" table) | |||
| table) | |||
| "Elixir mode syntax table.") | |||
| (defconst elixir-smie-grammar | |||
| (smie-prec2->grammar | |||
| (smie-merge-prec2s | |||
| (smie-bnf->prec2 | |||
| '((id) | |||
| (statements (statement) | |||
| (statement ";" statements)) | |||
| (statement ("def" non-block-expr "do" statements "end") | |||
| (non-block-expr "fn" match-statements "end") | |||
| (non-block-expr "do" statements "end") | |||
| ("if" non-block-expr "do" statements "else" statements "end") | |||
| ("if" non-block-expr "do" statements "end") | |||
| ("if" non-block-expr "COMMA" "do:" non-block-expr) | |||
| ("if" non-block-expr "COMMA" | |||
| "do:" non-block-expr "COMMA" | |||
| "else:" non-block-expr) | |||
| ("try" "do" statements "after" statements "end") | |||
| ("try" "do" statements "catch" match-statements "end") | |||
| ("try" "do" statements "rescue" match-statements "end") | |||
| ("try" "do" statements "end") | |||
| ("case" non-block-expr "do" match-statements "end")) | |||
| (non-block-expr (non-block-expr "OP" non-block-expr) | |||
| (non-block-expr "COMMA" non-block-expr) | |||
| ("(" non-block-expr ")") | |||
| ("{" non-block-expr "}") | |||
| ("[" non-block-expr "]") | |||
| ("STRING")) | |||
| (match-statements (match-statement "MATCH-STATEMENT-DELIMITER" | |||
| match-statements) | |||
| (match-statement)) | |||
| (match-statement (non-block-expr "->" statements))) | |||
| '((assoc "if" "do:" "else:") | |||
| (assoc "COMMA") | |||
| (left "OP"))) | |||
| (smie-precs->prec2 | |||
| '((left "||") | |||
| (left "&&") | |||
| (nonassoc "=~" "===" "!==" "==" "!=" "<=" ">=" "<" ">") | |||
| (left "+" "-" "<<<" ">>>" "^^^" "~~~" "&&&" "|||") | |||
| (left "*" "/")))))) | |||
| (defvar elixir-smie--operator-regexp | |||
| (rx (or "<<<" ">>>" "^^^" "~~~" "&&&" "|||" "===" "!==" "==" "!=" "<=" | |||
| "=" ">=" "<" ">" "&&" "||" "<>" "++" "--" "//" "/>" "=~" "|>"))) | |||
| (defvar elixir-smie--binary-sequence-regexp | |||
| (rx (or "<<" ">>"))) | |||
| (defvar elixir-smie--block-operator-regexp | |||
| (rx "->" (0+ nonl))) | |||
| (defvar elixir-smie--spaces-til-eol-regexp | |||
| (rx (and (1+ space) eol)) | |||
| "Regex representing one or more whitespace characters concluding with eol.") | |||
| (defvar elixir-smie--comment-regexp | |||
| (rx (and (0+ space) "#" (0+ not-newline))) | |||
| "Regex matching comments.") | |||
| (defvar elixir-smie-indent-basic 2) | |||
| (defmacro elixir-smie-debug (message &rest format-args) | |||
| `(progn | |||
| (when elixir-smie-verbose-p | |||
| (message (format ,message ,@format-args))) | |||
| nil)) | |||
| (defun elixir-smie--implicit-semi-p () | |||
| (not (or (memq (char-before) '(?\{ ?\[)) | |||
| (looking-back elixir-smie--operator-regexp (- (point) 3) t)))) | |||
| (defun elixir-smie--semi-ends-match () | |||
| "Return non-nil if the current line concludes a match block." | |||
| (when (not (eobp)) | |||
| (save-excursion | |||
| ;; Warning: Recursion. | |||
| ;; This is easy though. | |||
| ;; 1. If we're at a blank line, move forward a character. This takes us to | |||
| ;; the next line. | |||
| ;; 2. If we're not at the end of the buffer, call this function again. | |||
| ;; (Otherwise, return nil.) | |||
| ;; The point here is that we want to treat blank lines as a single semi- | |||
| ;; colon when it comes to detecting the end of match statements. This could | |||
| ;; also be handled by a `while' expression or some other looping mechanism. | |||
| (cl-flet ((self-call () | |||
| (if (< (point) (point-max)) | |||
| (elixir-smie--semi-ends-match) | |||
| nil))) | |||
| (cond | |||
| ((and (eolp) (bolp)) | |||
| (forward-char) | |||
| (self-call)) | |||
| ((looking-at elixir-smie--spaces-til-eol-regexp) | |||
| (move-beginning-of-line 2) | |||
| (self-call)) | |||
| ;; And if we're NOT on a blank line, move to the end of the line, and see | |||
| ;; if we're looking back at a block operator. | |||
| (t (move-end-of-line 1) | |||
| (looking-back elixir-smie--block-operator-regexp))))))) | |||
| (defun elixir-smie--same-line-as-parent (parent-pos child-pos) | |||
| "Return non-nil if `child-pos' is on same line as `parent-pos'." | |||
| (= (line-number-at-pos parent-pos) (line-number-at-pos child-pos))) | |||
| (defun elixir-smie-forward-token () | |||
| (cond | |||
| ;; If there is nothing but whitespace between the last token and eol, emit | |||
| ;; a semicolon. | |||
| ((looking-at elixir-smie--spaces-til-eol-regexp) | |||
| (goto-char (match-end 0)) | |||
| ";") | |||
| ((and (or (looking-at elixir-smie--comment-regexp) | |||
| (looking-at "[\n#]")) | |||
| (elixir-smie--implicit-semi-p)) | |||
| (when (not (save-excursion | |||
| (forward-comment 1) | |||
| (eobp))) | |||
| (if (eolp) (forward-char 1) (forward-comment 1))) | |||
| ;; Note: `elixir-smie--semi-ends-match' will be called when the point is at | |||
| ;; the beginning of a new line. Keep that in mind. | |||
| (if (elixir-smie--semi-ends-match) | |||
| "MATCH-STATEMENT-DELIMITER" | |||
| ";")) | |||
| ((looking-at elixir-smie--block-operator-regexp) | |||
| (goto-char (match-end 0)) | |||
| "->") | |||
| ((looking-at elixir-smie--operator-regexp) | |||
| (goto-char (match-end 0)) | |||
| "OP") | |||
| (t (smie-default-forward-token)))) | |||
| (defun elixir-smie-backward-token () | |||
| (let ((pos (point))) | |||
| (forward-comment (- (point))) | |||
| (cond | |||
| ((and (> pos (line-end-position)) | |||
| (elixir-smie--implicit-semi-p)) | |||
| (if (elixir-smie--semi-ends-match) | |||
| "MATCH-STATEMENT-DELIMITER" | |||
| ";")) | |||
| ((looking-back elixir-smie--block-operator-regexp (- (point) 3) t) | |||
| (goto-char (match-beginning 0)) | |||
| "->") | |||
| ((looking-back elixir-smie--binary-sequence-regexp (- (point) 3) t) | |||
| (goto-char (match-beginning 0)) | |||
| "OP") | |||
| ((looking-back elixir-smie--operator-regexp (- (point) 3) t) | |||
| (goto-char (match-beginning 0)) | |||
| "OP") | |||
| (t (smie-default-backward-token))))) | |||
| (defun verbose-elixir-smie-rules (kind token) | |||
| (let ((value (elixir-smie-rules kind token))) | |||
| (elixir-smie-debug "%s '%s'; sibling-p:%s parent:%s prev-is-OP:%s hanging:%s == %s" kind token | |||
| (ignore-errors (smie-rule-sibling-p)) | |||
| (ignore-errors smie--parent) | |||
| (ignore-errors (smie-rule-prev-p "OP")) | |||
| (ignore-errors (smie-rule-hanging-p)) | |||
| value) | |||
| value)) | |||
| (defun elixir-smie-rules (kind token) | |||
| (pcase (cons kind token) | |||
| (`(:before . "OP") | |||
| (when (and (not (smie-rule-hanging-p)) | |||
| (smie-rule-prev-p "OP")) | |||
| -2)) | |||
| (`(:after . "OP") | |||
| (cond | |||
| ((smie-rule-sibling-p) nil) | |||
| ((smie-rule-hanging-p) (smie-rule-parent elixir-smie-indent-basic)) | |||
| (t (smie-rule-parent)))) | |||
| (`(:before . "MATCH-STATEMENT-DELIMITER") | |||
| (cond | |||
| ((and (not (smie-rule-sibling-p)) | |||
| (smie-rule-hanging-p)) | |||
| (smie-rule-parent elixir-smie-indent-basic)))) | |||
| (`(:before . "fn") | |||
| (smie-rule-parent)) | |||
| (`(:before . "end") | |||
| (smie-rule-parent)) | |||
| ;; Closing paren on the other line | |||
| (`(:before . "(") | |||
| (smie-rule-parent)) | |||
| (`(:before . "[") | |||
| (cond | |||
| ((smie-rule-hanging-p) | |||
| (smie-rule-parent)))) | |||
| (`(:after . "[") | |||
| (cond | |||
| ((smie-rule-hanging-p) | |||
| (smie-rule-parent elixir-smie-indent-basic)))) | |||
| (`(:before . "->") | |||
| (cond | |||
| ((smie-rule-hanging-p) | |||
| (smie-rule-parent elixir-smie-indent-basic)))) | |||
| (`(:after . "->") | |||
| (cond | |||
| ;; This first condition is kind of complicated so I'll try to make this | |||
| ;; comment as clear as possible. | |||
| ;; "If `->' is the last thing on the line, and its parent token | |||
| ;; is `fn' ..." | |||
| ((and (smie-rule-hanging-p) | |||
| (smie-rule-parent-p "fn")) | |||
| ;; "... and if: | |||
| ;; 1. `smie--parent' is non-nil | |||
| ;; 2. the `->' token in question is on the same line as its parent (if | |||
| ;; the logic has gotten this far, its parent will be `fn') | |||
| ;; ... then indent the line after the `->' aligned with the | |||
| ;; parent, offset by `elixir-smie-indent-basic'." | |||
| (if (and smie--parent (elixir-smie--same-line-as-parent | |||
| (nth 1 smie--parent) | |||
| (point))) | |||
| (smie-rule-parent elixir-smie-indent-basic) | |||
| elixir-smie-indent-basic)) | |||
| ;; Otherwise, if just indent by two. | |||
| ((smie-rule-hanging-p) | |||
| (cond | |||
| ((smie-rule-parent-p "after" "catch" "do" "rescue" "try") | |||
| elixir-smie-indent-basic) | |||
| (t | |||
| (smie-rule-parent elixir-smie-indent-basic)))))) | |||
| (`(:before . ";") | |||
| (cond | |||
| ((smie-rule-parent-p "after" "catch" "def" "defmodule" "defp" "do" "else" | |||
| "fn" "if" "rescue" "try" "unless") | |||
| (smie-rule-parent elixir-smie-indent-basic)))) | |||
| (`(:after . ";") | |||
| (cond | |||
| ((smie-rule-parent-p "def") | |||
| (smie-rule-parent)) | |||
| ((smie-rule-parent-p "if") | |||
| (smie-rule-parent)) | |||
| ((and (smie-rule-parent-p "(") | |||
| (save-excursion | |||
| (goto-char (cadr smie--parent)) | |||
| (smie-rule-hanging-p))) | |||
| (smie-rule-parent elixir-smie-indent-basic)))))) | |||
| (provide 'elixir-smie) | |||
| ;;; elixir-smie.el ends here | |||
| @ -0,0 +1,2 @@ | |||
| Provides font-locking, indentation and navigation support | |||
| for the Elixir programming language. | |||
| @ -0,0 +1,674 @@ | |||
| GNU GENERAL PUBLIC LICENSE | |||
| Version 3, 29 June 2007 | |||
| Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> | |||
| Everyone is permitted to copy and distribute verbatim copies | |||
| of this license document, but changing it is not allowed. | |||
| Preamble | |||
| The GNU General Public License is a free, copyleft license for | |||
| software and other kinds of works. | |||
| The licenses for most software and other practical works are designed | |||
| to take away your freedom to share and change the works. By contrast, | |||
| the GNU General Public License is intended to guarantee your freedom to | |||
| share and change all versions of a program--to make sure it remains free | |||
| software for all its users. We, the Free Software Foundation, use the | |||
| GNU General Public License for most of our software; it applies also to | |||
| any other work released this way by its authors. You can apply it to | |||
| your programs, too. | |||
| When we speak of free software, we are referring to freedom, not | |||
| price. Our General Public Licenses are designed to make sure that you | |||
| have the freedom to distribute copies of free software (and charge for | |||
| them if you wish), that you receive source code or can get it if you | |||
| want it, that you can change the software or use pieces of it in new | |||
| free programs, and that you know you can do these things. | |||
| To protect your rights, we need to prevent others from denying you | |||
| these rights or asking you to surrender the rights. Therefore, you have | |||
| certain responsibilities if you distribute copies of the software, or if | |||
| you modify it: responsibilities to respect the freedom of others. | |||
| For example, if you distribute copies of such a program, whether | |||
| gratis or for a fee, you must pass on to the recipients the same | |||
| freedoms that you received. You must make sure that they, too, receive | |||
| or can get the source code. And you must show them these terms so they | |||
| know their rights. | |||
| Developers that use the GNU GPL protect your rights with two steps: | |||
| (1) assert copyright on the software, and (2) offer you this License | |||
| giving you legal permission to copy, distribute and/or modify it. | |||
| For the developers' and authors' protection, the GPL clearly explains | |||
| that there is no warranty for this free software. For both users' and | |||
| authors' sake, the GPL requires that modified versions be marked as | |||
| changed, so that their problems will not be attributed erroneously to | |||
| authors of previous versions. | |||
| Some devices are designed to deny users access to install or run | |||
| modified versions of the software inside them, although the manufacturer | |||
| can do so. This is fundamentally incompatible with the aim of | |||
| protecting users' freedom to change the software. The systematic | |||
| pattern of such abuse occurs in the area of products for individuals to | |||
| use, which is precisely where it is most unacceptable. Therefore, we | |||
| have designed this version of the GPL to prohibit the practice for those | |||
| products. If such problems arise substantially in other domains, we | |||
| stand ready to extend this provision to those domains in future versions | |||
| of the GPL, as needed to protect the freedom of users. | |||
| Finally, every program is threatened constantly by software patents. | |||
| States should not allow patents to restrict development and use of | |||
| software on general-purpose computers, but in those that do, we wish to | |||
| avoid the special danger that patents applied to a free program could | |||
| make it effectively proprietary. To prevent this, the GPL assures that | |||
| patents cannot be used to render the program non-free. | |||
| The precise terms and conditions for copying, distribution and | |||
| modification follow. | |||
| TERMS AND CONDITIONS | |||
| 0. Definitions. | |||
| "This License" refers to version 3 of the GNU General Public License. | |||
| "Copyright" also means copyright-like laws that apply to other kinds of | |||
| works, such as semiconductor masks. | |||
| "The Program" refers to any copyrightable work licensed under this | |||
| License. Each licensee is addressed as "you". "Licensees" and | |||
| "recipients" may be individuals or organizations. | |||
| To "modify" a work means to copy from or adapt all or part of the work | |||
| in a fashion requiring copyright permission, other than the making of an | |||
| exact copy. The resulting work is called a "modified version" of the | |||
| earlier work or a work "based on" the earlier work. | |||
| A "covered work" means either the unmodified Program or a work based | |||
| on the Program. | |||
| To "propagate" a work means to do anything with it that, without | |||
| permission, would make you directly or secondarily liable for | |||
| infringement under applicable copyright law, except executing it on a | |||
| computer or modifying a private copy. Propagation includes copying, | |||
| distribution (with or without modification), making available to the | |||
| public, and in some countries other activities as well. | |||
| To "convey" a work means any kind of propagation that enables other | |||
| parties to make or receive copies. Mere interaction with a user through | |||
| a computer network, with no transfer of a copy, is not conveying. | |||
| An interactive user interface displays "Appropriate Legal Notices" | |||
| to the extent that it includes a convenient and prominently visible | |||
| feature that (1) displays an appropriate copyright notice, and (2) | |||
| tells the user that there is no warranty for the work (except to the | |||
| extent that warranties are provided), that licensees may convey the | |||
| work under this License, and how to view a copy of this License. If | |||
| the interface presents a list of user commands or options, such as a | |||
| menu, a prominent item in the list meets this criterion. | |||
| 1. Source Code. | |||
| The "source code" for a work means the preferred form of the work | |||
| for making modifications to it. "Object code" means any non-source | |||
| form of a work. | |||
| A "Standard Interface" means an interface that either is an official | |||
| standard defined by a recognized standards body, or, in the case of | |||
| interfaces specified for a particular programming language, one that | |||
| is widely used among developers working in that language. | |||
| The "System Libraries" of an executable work include anything, other | |||
| than the work as a whole, that (a) is included in the normal form of | |||
| packaging a Major Component, but which is not part of that Major | |||
| Component, and (b) serves only to enable use of the work with that | |||
| Major Component, or to implement a Standard Interface for which an | |||
| implementation is available to the public in source code form. A | |||
| "Major Component", in this context, means a major essential component | |||
| (kernel, window system, and so on) of the specific operating system | |||
| (if any) on which the executable work runs, or a compiler used to | |||
| produce the work, or an object code interpreter used to run it. | |||
| The "Corresponding Source" for a work in object code form means all | |||
| the source code needed to generate, install, and (for an executable | |||
| work) run the object code and to modify the work, including scripts to | |||
| control those activities. However, it does not include the work's | |||
| System Libraries, or general-purpose tools or generally available free | |||
| programs which are used unmodified in performing those activities but | |||
| which are not part of the work. For example, Corresponding Source | |||
| includes interface definition files associated with source files for | |||
| the work, and the source code for shared libraries and dynamically | |||
| linked subprograms that the work is specifically designed to require, | |||
| such as by intimate data communication or control flow between those | |||
| subprograms and other parts of the work. | |||
| The Corresponding Source need not include anything that users | |||
| can regenerate automatically from other parts of the Corresponding | |||
| Source. | |||
| The Corresponding Source for a work in source code form is that | |||
| same work. | |||
| 2. Basic Permissions. | |||
| All rights granted under this License are granted for the term of | |||
| copyright on the Program, and are irrevocable provided the stated | |||
| conditions are met. This License explicitly affirms your unlimited | |||
| permission to run the unmodified Program. The output from running a | |||
| covered work is covered by this License only if the output, given its | |||
| content, constitutes a covered work. This License acknowledges your | |||
| rights of fair use or other equivalent, as provided by copyright law. | |||
| You may make, run and propagate covered works that you do not | |||
| convey, without conditions so long as your license otherwise remains | |||
| in force. You may convey covered works to others for the sole purpose | |||
| of having them make modifications exclusively for you, or provide you | |||
| with facilities for running those works, provided that you comply with | |||
| the terms of this License in conveying all material for which you do | |||
| not control copyright. Those thus making or running the covered works | |||
| for you must do so exclusively on your behalf, under your direction | |||
| and control, on terms that prohibit them from making any copies of | |||
| your copyrighted material outside their relationship with you. | |||
| Conveying under any other circumstances is permitted solely under | |||
| the conditions stated below. Sublicensing is not allowed; section 10 | |||
| makes it unnecessary. | |||
| 3. Protecting Users' Legal Rights From Anti-Circumvention Law. | |||
| No covered work shall be deemed part of an effective technological | |||
| measure under any applicable law fulfilling obligations under article | |||
| 11 of the WIPO copyright treaty adopted on 20 December 1996, or | |||
| similar laws prohibiting or restricting circumvention of such | |||
| measures. | |||
| When you convey a covered work, you waive any legal power to forbid | |||
| circumvention of technological measures to the extent such circumvention | |||
| is effected by exercising rights under this License with respect to | |||
| the covered work, and you disclaim any intention to limit operation or | |||
| modification of the work as a means of enforcing, against the work's | |||
| users, your or third parties' legal rights to forbid circumvention of | |||
| technological measures. | |||
| 4. Conveying Verbatim Copies. | |||
| You may convey verbatim copies of the Program's source code as you | |||
| receive it, in any medium, provided that you conspicuously and | |||
| appropriately publish on each copy an appropriate copyright notice; | |||
| keep intact all notices stating that this License and any | |||
| non-permissive terms added in accord with section 7 apply to the code; | |||
| keep intact all notices of the absence of any warranty; and give all | |||
| recipients a copy of this License along with the Program. | |||
| You may charge any price or no price for each copy that you convey, | |||
| and you may offer support or warranty protection for a fee. | |||
| 5. Conveying Modified Source Versions. | |||
| You may convey a work based on the Program, or the modifications to | |||
| produce it from the Program, in the form of source code under the | |||
| terms of section 4, provided that you also meet all of these conditions: | |||
| a) The work must carry prominent notices stating that you modified | |||
| it, and giving a relevant date. | |||
| b) The work must carry prominent notices stating that it is | |||
| released under this License and any conditions added under section | |||
| 7. This requirement modifies the requirement in section 4 to | |||
| "keep intact all notices". | |||
| c) You must license the entire work, as a whole, under this | |||
| License to anyone who comes into possession of a copy. This | |||
| License will therefore apply, along with any applicable section 7 | |||
| additional terms, to the whole of the work, and all its parts, | |||
| regardless of how they are packaged. This License gives no | |||
| permission to license the work in any other way, but it does not | |||
| invalidate such permission if you have separately received it. | |||
| d) If the work has interactive user interfaces, each must display | |||
| Appropriate Legal Notices; however, if the Program has interactive | |||
| interfaces that do not display Appropriate Legal Notices, your | |||
| work need not make them do so. | |||
| A compilation of a covered work with other separate and independent | |||
| works, which are not by their nature extensions of the covered work, | |||
| and which are not combined with it such as to form a larger program, | |||
| in or on a volume of a storage or distribution medium, is called an | |||
| "aggregate" if the compilation and its resulting copyright are not | |||
| used to limit the access or legal rights of the compilation's users | |||
| beyond what the individual works permit. Inclusion of a covered work | |||
| in an aggregate does not cause this License to apply to the other | |||
| parts of the aggregate. | |||
| 6. Conveying Non-Source Forms. | |||
| You may convey a covered work in object code form under the terms | |||
| of sections 4 and 5, provided that you also convey the | |||
| machine-readable Corresponding Source under the terms of this License, | |||
| in one of these ways: | |||
| a) Convey the object code in, or embodied in, a physical product | |||
| (including a physical distribution medium), accompanied by the | |||
| Corresponding Source fixed on a durable physical medium | |||
| customarily used for software interchange. | |||
| b) Convey the object code in, or embodied in, a physical product | |||
| (including a physical distribution medium), accompanied by a | |||
| written offer, valid for at least three years and valid for as | |||
| long as you offer spare parts or customer support for that product | |||
| model, to give anyone who possesses the object code either (1) a | |||
| copy of the Corresponding Source for all the software in the | |||
| product that is covered by this License, on a durable physical | |||
| medium customarily used for software interchange, for a price no | |||
| more than your reasonable cost of physically performing this | |||
| conveying of source, or (2) access to copy the | |||
| Corresponding Source from a network server at no charge. | |||
| c) Convey individual copies of the object code with a copy of the | |||
| written offer to provide the Corresponding Source. This | |||
| alternative is allowed only occasionally and noncommercially, and | |||
| only if you received the object code with such an offer, in accord | |||
| with subsection 6b. | |||
| d) Convey the object code by offering access from a designated | |||
| place (gratis or for a charge), and offer equivalent access to the | |||
| Corresponding Source in the same way through the same place at no | |||
| further charge. You need not require recipients to copy the | |||
| Corresponding Source along with the object code. If the place to | |||
| copy the object code is a network server, the Corresponding Source | |||
| may be on a different server (operated by you or a third party) | |||
| that supports equivalent copying facilities, provided you maintain | |||
| clear directions next to the object code saying where to find the | |||
| Corresponding Source. Regardless of what server hosts the | |||
| Corresponding Source, you remain obligated to ensure that it is | |||
| available for as long as needed to satisfy these requirements. | |||
| e) Convey the object code using peer-to-peer transmission, provided | |||
| you inform other peers where the object code and Corresponding | |||
| Source of the work are being offered to the general public at no | |||
| charge under subsection 6d. | |||
| A separable portion of the object code, whose source code is excluded | |||
| from the Corresponding Source as a System Library, need not be | |||
| included in conveying the object code work. | |||
| A "User Product" is either (1) a "consumer product", which means any | |||
| tangible personal property which is normally used for personal, family, | |||
| or household purposes, or (2) anything designed or sold for incorporation | |||
| into a dwelling. In determining whether a product is a consumer product, | |||
| doubtful cases shall be resolved in favor of coverage. For a particular | |||
| product received by a particular user, "normally used" refers to a | |||
| typical or common use of that class of product, regardless of the status | |||
| of the particular user or of the way in which the particular user | |||
| actually uses, or expects or is expected to use, the product. A product | |||
| is a consumer product regardless of whether the product has substantial | |||
| commercial, industrial or non-consumer uses, unless such uses represent | |||
| the only significant mode of use of the product. | |||
| "Installation Information" for a User Product means any methods, | |||
| procedures, authorization keys, or other information required to install | |||
| and execute modified versions of a covered work in that User Product from | |||
| a modified version of its Corresponding Source. The information must | |||
| suffice to ensure that the continued functioning of the modified object | |||
| code is in no case prevented or interfered with solely because | |||
| modification has been made. | |||
| If you convey an object code work under this section in, or with, or | |||
| specifically for use in, a User Product, and the conveying occurs as | |||
| part of a transaction in which the right of possession and use of the | |||
| User Product is transferred to the recipient in perpetuity or for a | |||
| fixed term (regardless of how the transaction is characterized), the | |||
| Corresponding Source conveyed under this section must be accompanied | |||
| by the Installation Information. But this requirement does not apply | |||
| if neither you nor any third party retains the ability to install | |||
| modified object code on the User Product (for example, the work has | |||
| been installed in ROM). | |||
| The requirement to provide Installation Information does not include a | |||
| requirement to continue to provide support service, warranty, or updates | |||
| for a work that has been modified or installed by the recipient, or for | |||
| the User Product in which it has been modified or installed. Access to a | |||
| network may be denied when the modification itself materially and | |||
| adversely affects the operation of the network or violates the rules and | |||
| protocols for communication across the network. | |||
| Corresponding Source conveyed, and Installation Information provided, | |||
| in accord with this section must be in a format that is publicly | |||
| documented (and with an implementation available to the public in | |||
| source code form), and must require no special password or key for | |||
| unpacking, reading or copying. | |||
| 7. Additional Terms. | |||
| "Additional permissions" are terms that supplement the terms of this | |||
| License by making exceptions from one or more of its conditions. | |||
| Additional permissions that are applicable to the entire Program shall | |||
| be treated as though they were included in this License, to the extent | |||
| that they are valid under applicable law. If additional permissions | |||
| apply only to part of the Program, that part may be used separately | |||
| under those permissions, but the entire Program remains governed by | |||
| this License without regard to the additional permissions. | |||
| When you convey a copy of a covered work, you may at your option | |||
| remove any additional permissions from that copy, or from any part of | |||
| it. (Additional permissions may be written to require their own | |||
| removal in certain cases when you modify the work.) You may place | |||
| additional permissions on material, added by you to a covered work, | |||
| for which you have or can give appropriate copyright permission. | |||
| Notwithstanding any other provision of this License, for material you | |||
| add to a covered work, you may (if authorized by the copyright holders of | |||
| that material) supplement the terms of this License with terms: | |||
| a) Disclaiming warranty or limiting liability differently from the | |||
| terms of sections 15 and 16 of this License; or | |||
| b) Requiring preservation of specified reasonable legal notices or | |||
| author attributions in that material or in the Appropriate Legal | |||
| Notices displayed by works containing it; or | |||
| c) Prohibiting misrepresentation of the origin of that material, or | |||
| requiring that modified versions of such material be marked in | |||
| reasonable ways as different from the original version; or | |||
| d) Limiting the use for publicity purposes of names of licensors or | |||
| authors of the material; or | |||
| e) Declining to grant rights under trademark law for use of some | |||
| trade names, trademarks, or service marks; or | |||
| f) Requiring indemnification of licensors and authors of that | |||
| material by anyone who conveys the material (or modified versions of | |||
| it) with contractual assumptions of liability to the recipient, for | |||
| any liability that these contractual assumptions directly impose on | |||
| those licensors and authors. | |||
| All other non-permissive additional terms are considered "further | |||
| restrictions" within the meaning of section 10. If the Program as you | |||
| received it, or any part of it, contains a notice stating that it is | |||
| governed by this License along with a term that is a further | |||
| restriction, you may remove that term. If a license document contains | |||
| a further restriction but permits relicensing or conveying under this | |||
| License, you may add to a covered work material governed by the terms | |||
| of that license document, provided that the further restriction does | |||
| not survive such relicensing or conveying. | |||
| If you add terms to a covered work in accord with this section, you | |||
| must place, in the relevant source files, a statement of the | |||
| additional terms that apply to those files, or a notice indicating | |||
| where to find the applicable terms. | |||
| Additional terms, permissive or non-permissive, may be stated in the | |||
| form of a separately written license, or stated as exceptions; | |||
| the above requirements apply either way. | |||
| 8. Termination. | |||
| You may not propagate or modify a covered work except as expressly | |||
| provided under this License. Any attempt otherwise to propagate or | |||
| modify it is void, and will automatically terminate your rights under | |||
| this License (including any patent licenses granted under the third | |||
| paragraph of section 11). | |||
| However, if you cease all violation of this License, then your | |||
| license from a particular copyright holder is reinstated (a) | |||
| provisionally, unless and until the copyright holder explicitly and | |||
| finally terminates your license, and (b) permanently, if the copyright | |||
| holder fails to notify you of the violation by some reasonable means | |||
| prior to 60 days after the cessation. | |||
| Moreover, your license from a particular copyright holder is | |||
| reinstated permanently if the copyright holder notifies you of the | |||
| violation by some reasonable means, this is the first time you have | |||
| received notice of violation of this License (for any work) from that | |||
| copyright holder, and you cure the violation prior to 30 days after | |||
| your receipt of the notice. | |||
| Termination of your rights under this section does not terminate the | |||
| licenses of parties who have received copies or rights from you under | |||
| this License. If your rights have been terminated and not permanently | |||
| reinstated, you do not qualify to receive new licenses for the same | |||
| material under section 10. | |||
| 9. Acceptance Not Required for Having Copies. | |||
| You are not required to accept this License in order to receive or | |||
| run a copy of the Program. Ancillary propagation of a covered work | |||
| occurring solely as a consequence of using peer-to-peer transmission | |||
| to receive a copy likewise does not require acceptance. However, | |||
| nothing other than this License grants you permission to propagate or | |||
| modify any covered work. These actions infringe copyright if you do | |||
| not accept this License. Therefore, by modifying or propagating a | |||
| covered work, you indicate your acceptance of this License to do so. | |||
| 10. Automatic Licensing of Downstream Recipients. | |||
| Each time you convey a covered work, the recipient automatically | |||
| receives a license from the original licensors, to run, modify and | |||
| propagate that work, subject to this License. You are not responsible | |||
| for enforcing compliance by third parties with this License. | |||
| An "entity transaction" is a transaction transferring control of an | |||
| organization, or substantially all assets of one, or subdividing an | |||
| organization, or merging organizations. If propagation of a covered | |||
| work results from an entity transaction, each party to that | |||
| transaction who receives a copy of the work also receives whatever | |||
| licenses to the work the party's predecessor in interest had or could | |||
| give under the previous paragraph, plus a right to possession of the | |||
| Corresponding Source of the work from the predecessor in interest, if | |||
| the predecessor has it or can get it with reasonable efforts. | |||
| You may not impose any further restrictions on the exercise of the | |||
| rights granted or affirmed under this License. For example, you may | |||
| not impose a license fee, royalty, or other charge for exercise of | |||
| rights granted under this License, and you may not initiate litigation | |||
| (including a cross-claim or counterclaim in a lawsuit) alleging that | |||
| any patent claim is infringed by making, using, selling, offering for | |||
| sale, or importing the Program or any portion of it. | |||
| 11. Patents. | |||
| A "contributor" is a copyright holder who authorizes use under this | |||
| License of the Program or a work on which the Program is based. The | |||
| work thus licensed is called the contributor's "contributor version". | |||
| A contributor's "essential patent claims" are all patent claims | |||
| owned or controlled by the contributor, whether already acquired or | |||
| hereafter acquired, that would be infringed by some manner, permitted | |||
| by this License, of making, using, or selling its contributor version, | |||
| but do not include claims that would be infringed only as a | |||
| consequence of further modification of the contributor version. For | |||
| purposes of this definition, "control" includes the right to grant | |||
| patent sublicenses in a manner consistent with the requirements of | |||
| this License. | |||
| Each contributor grants you a non-exclusive, worldwide, royalty-free | |||
| patent license under the contributor's essential patent claims, to | |||
| make, use, sell, offer for sale, import and otherwise run, modify and | |||
| propagate the contents of its contributor version. | |||
| In the following three paragraphs, a "patent license" is any express | |||
| agreement or commitment, however denominated, not to enforce a patent | |||
| (such as an express permission to practice a patent or covenant not to | |||
| sue for patent infringement). To "grant" such a patent license to a | |||
| party means to make such an agreement or commitment not to enforce a | |||
| patent against the party. | |||
| If you convey a covered work, knowingly relying on a patent license, | |||
| and the Corresponding Source of the work is not available for anyone | |||
| to copy, free of charge and under the terms of this License, through a | |||
| publicly available network server or other readily accessible means, | |||
| then you must either (1) cause the Corresponding Source to be so | |||
| available, or (2) arrange to deprive yourself of the benefit of the | |||
| patent license for this particular work, or (3) arrange, in a manner | |||
| consistent with the requirements of this License, to extend the patent | |||
| license to downstream recipients. "Knowingly relying" means you have | |||
| actual knowledge that, but for the patent license, your conveying the | |||
| covered work in a country, or your recipient's use of the covered work | |||
| in a country, would infringe one or more identifiable patents in that | |||
| country that you have reason to believe are valid. | |||
| If, pursuant to or in connection with a single transaction or | |||
| arrangement, you convey, or propagate by procuring conveyance of, a | |||
| covered work, and grant a patent license to some of the parties | |||
| receiving the covered work authorizing them to use, propagate, modify | |||
| or convey a specific copy of the covered work, then the patent license | |||
| you grant is automatically extended to all recipients of the covered | |||
| work and works based on it. | |||
| A patent license is "discriminatory" if it does not include within | |||
| the scope of its coverage, prohibits the exercise of, or is | |||
| conditioned on the non-exercise of one or more of the rights that are | |||
| specifically granted under this License. You may not convey a covered | |||
| work if you are a party to an arrangement with a third party that is | |||
| in the business of distributing software, under which you make payment | |||
| to the third party based on the extent of your activity of conveying | |||
| the work, and under which the third party grants, to any of the | |||
| parties who would receive the covered work from you, a discriminatory | |||
| patent license (a) in connection with copies of the covered work | |||
| conveyed by you (or copies made from those copies), or (b) primarily | |||
| for and in connection with specific products or compilations that | |||
| contain the covered work, unless you entered into that arrangement, | |||
| or that patent license was granted, prior to 28 March 2007. | |||
| Nothing in this License shall be construed as excluding or limiting | |||
| any implied license or other defenses to infringement that may | |||
| otherwise be available to you under applicable patent law. | |||
| 12. No Surrender of Others' Freedom. | |||
| If conditions are imposed on you (whether by court order, agreement or | |||
| otherwise) that contradict the conditions of this License, they do not | |||
| excuse you from the conditions of this License. If you cannot convey a | |||
| covered work so as to satisfy simultaneously your obligations under this | |||
| License and any other pertinent obligations, then as a consequence you may | |||
| not convey it at all. For example, if you agree to terms that obligate you | |||
| to collect a royalty for further conveying from those to whom you convey | |||
| the Program, the only way you could satisfy both those terms and this | |||
| License would be to refrain entirely from conveying the Program. | |||
| 13. Use with the GNU Affero General Public License. | |||
| Notwithstanding any other provision of this License, you have | |||
| permission to link or combine any covered work with a work licensed | |||
| under version 3 of the GNU Affero General Public License into a single | |||
| combined work, and to convey the resulting work. The terms of this | |||
| License will continue to apply to the part which is the covered work, | |||
| but the special requirements of the GNU Affero General Public License, | |||
| section 13, concerning interaction through a network will apply to the | |||
| combination as such. | |||
| 14. Revised Versions of this License. | |||
| The Free Software Foundation may publish revised and/or new versions of | |||
| the GNU General Public License from time to time. Such new versions will | |||
| be similar in spirit to the present version, but may differ in detail to | |||
| address new problems or concerns. | |||
| Each version is given a distinguishing version number. If the | |||
| Program specifies that a certain numbered version of the GNU General | |||
| Public License "or any later version" applies to it, you have the | |||
| option of following the terms and conditions either of that numbered | |||
| version or of any later version published by the Free Software | |||
| Foundation. If the Program does not specify a version number of the | |||
| GNU General Public License, you may choose any version ever published | |||
| by the Free Software Foundation. | |||
| If the Program specifies that a proxy can decide which future | |||
| versions of the GNU General Public License can be used, that proxy's | |||
| public statement of acceptance of a version permanently authorizes you | |||
| to choose that version for the Program. | |||
| Later license versions may give you additional or different | |||
| permissions. However, no additional obligations are imposed on any | |||
| author or copyright holder as a result of your choosing to follow a | |||
| later version. | |||
| 15. Disclaimer of Warranty. | |||
| THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY | |||
| APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT | |||
| HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY | |||
| OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, | |||
| THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | |||
| PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM | |||
| IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF | |||
| ALL NECESSARY SERVICING, REPAIR OR CORRECTION. | |||
| 16. Limitation of Liability. | |||
| IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING | |||
| WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS | |||
| THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY | |||
| GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE | |||
| USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF | |||
| DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD | |||
| PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), | |||
| EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF | |||
| SUCH DAMAGES. | |||
| 17. Interpretation of Sections 15 and 16. | |||
| If the disclaimer of warranty and limitation of liability provided | |||
| above cannot be given local legal effect according to their terms, | |||
| reviewing courts shall apply local law that most closely approximates | |||
| an absolute waiver of all civil liability in connection with the | |||
| Program, unless a warranty or assumption of liability accompanies a | |||
| copy of the Program in return for a fee. | |||
| END OF TERMS AND CONDITIONS | |||
| How to Apply These Terms to Your New Programs | |||
| If you develop a new program, and you want it to be of the greatest | |||
| possible use to the public, the best way to achieve this is to make it | |||
| free software which everyone can redistribute and change under these terms. | |||
| To do so, attach the following notices to the program. It is safest | |||
| to attach them to the start of each source file to most effectively | |||
| state the exclusion of warranty; and each file should have at least | |||
| the "copyright" line and a pointer to where the full notice is found. | |||
| <one line to give the program's name and a brief idea of what it does.> | |||
| Copyright (C) <year> <name of author> | |||
| 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/>. | |||
| Also add information on how to contact you by electronic and paper mail. | |||
| If the program does terminal interaction, make it output a short | |||
| notice like this when it starts in an interactive mode: | |||
| <program> Copyright (C) <year> <name of author> | |||
| This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. | |||
| This is free software, and you are welcome to redistribute it | |||
| under certain conditions; type `show c' for details. | |||
| The hypothetical commands `show w' and `show c' should show the appropriate | |||
| parts of the General Public License. Of course, your program's commands | |||
| might be different; for a GUI interface, you would use an "about box". | |||
| You should also get your employer (if you work as a programmer) or school, | |||
| if any, to sign a "copyright disclaimer" for the program, if necessary. | |||
| For more information on this, and how to apply and follow the GNU GPL, see | |||
| <http://www.gnu.org/licenses/>. | |||
| The GNU General Public License does not permit incorporating your program | |||
| into proprietary programs. If your program is a subroutine library, you | |||
| may consider it more useful to permit linking proprietary applications with | |||
| the library. If this is what you want to do, use the GNU Lesser General | |||
| Public License instead of this License. But first, please read | |||
| <http://www.gnu.org/philosophy/why-not-lgpl.html>. | |||
| @ -0,0 +1,65 @@ | |||
| ======================================= | |||
| Elpy, the Emacs Lisp Python Environment | |||
| ======================================= | |||
| Elpy is an Emacs package to bring powerful Python editing to Emacs. It | |||
| combines a number of other packages, both written in Emacs Lisp as | |||
| well as Python. | |||
| .. image:: https://secure.travis-ci.org/jorgenschaefer/elpy.png?branch=master | |||
| :target: http://travis-ci.org/jorgenschaefer/elpy?branch=master | |||
| .. image:: https://coveralls.io/repos/jorgenschaefer/elpy/badge.png?branch=master | |||
| :target: https://coveralls.io/r/jorgenschaefer/elpy?branch=master | |||
| Documentation | |||
| ============= | |||
| Elpy is fully documented at readthedocs.org: | |||
| http://elpy.readthedocs.org/en/latest/index.html | |||
| Quick Installation | |||
| ================== | |||
| First, install the required Python packages::: | |||
| # Either of these | |||
| pip install rope | |||
| pip install jedi | |||
| # flake8 for code checks | |||
| pip install flake8 | |||
| # and importmagic for automatic imports | |||
| pip install importmagic | |||
| Evaluate this in your ``*scratch*`` buffer: | |||
| .. code-block:: lisp | |||
| (require 'package) | |||
| (add-to-list 'package-archives | |||
| '("elpy" . "http://jorgenschaefer.github.io/packages/")) | |||
| Then run ``M-x package-refresh-contents`` to load the contents of the | |||
| new repository, and ``M-x package-install RET elpy RET`` to install | |||
| elpy. | |||
| Finally, add the following to your ``.emacs``: | |||
| .. code-block:: lisp | |||
| (package-initialize) | |||
| (elpy-enable) | |||
| Done. | |||
| License | |||
| ======= | |||
| This project is free software: You can redistribute it and/or modify | |||
| it under the terms of the `GNU General Public License`__, either | |||
| version 3 of the License, or (at your option) any later version. | |||
| .. __: LICENSE | |||
| @ -0,0 +1,54 @@ | |||
| ;;; elpy-autoloads.el --- automatically extracted autoloads | |||
| ;; | |||
| ;;; Code: | |||
| (add-to-list 'load-path (or (file-name-directory #$) (car load-path))) | |||
| ;;;### (autoloads nil "elpy" "elpy.el" (21786 45609 0 0)) | |||
| ;;; Generated autoloads from elpy.el | |||
| (autoload 'elpy-enable "elpy" "\ | |||
| Enable Elpy in all future Python buffers. | |||
| \(fn &optional IGNORED)" t nil) | |||
| (autoload 'elpy-mode "elpy" "\ | |||
| Minor mode in Python buffers for the Emacs Lisp Python Environment. | |||
| This mode fully supports virtualenvs. Once you switch a | |||
| virtualenv using \\[pyvenv-workon], you can use | |||
| \\[elpy-rpc-restart] to make the elpy Python process use your | |||
| virtualenv. | |||
| See https://github.com/jorgenschaefer/elpy/wiki/Keybindings for a | |||
| more structured list. | |||
| \\{elpy-mode-map} | |||
| \(fn &optional ARG)" t nil) | |||
| (autoload 'elpy-config "elpy" "\ | |||
| Configure Elpy. | |||
| This function will pop up a configuration buffer, which is mostly | |||
| a customize buffer, but has some more options. | |||
| \(fn)" t nil) | |||
| (autoload 'elpy-version "elpy" "\ | |||
| Display the version of Elpy. | |||
| \(fn)" t nil) | |||
| ;;;*** | |||
| ;;;### (autoloads nil nil ("elpy-pkg.el" "elpy-refactor.el") (21786 | |||
| ;;;;;; 45609 742650 0)) | |||
| ;;;*** | |||
| ;; Local Variables: | |||
| ;; version-control: never | |||
| ;; no-byte-compile: t | |||
| ;; no-update-autoloads: t | |||
| ;; End: | |||
| ;;; elpy-autoloads.el ends here | |||
| @ -0,0 +1,8 @@ | |||
| (define-package "elpy" "1.7.1" | |||
| "Emacs Python Development Environment" | |||
| '((company "0.8.2") | |||
| (find-file-in-project "3.3") | |||
| (highlight-indentation "0.5.0") | |||
| (pyvenv "1.3") | |||
| (yasnippet "0.8.0")) | |||
| ) | |||
| @ -0,0 +1,285 @@ | |||
| ;;; elpy-refactor.el --- Refactoring mode for Elpy | |||
| ;; Copyright (C) 2013 Jorgen Schaefer | |||
| ;; Author: Jorgen Schaefer <contact@jorgenschaefer.de> | |||
| ;; URL: https://github.com/jorgenschaefer/elpy | |||
| ;; 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: | |||
| ;; This file provides an interface, including a major mode, to use | |||
| ;; refactoring options provided by the Rope library. | |||
| ;;; Code: | |||
| ;; We require elpy, but elpy loads us, so we shouldn't load it back. | |||
| ;; (require 'elpy) | |||
| (defvar elpy-refactor-changes nil | |||
| "Changes that will be commited on \\[elpy-refactor-commit].") | |||
| (make-variable-buffer-local 'elpy-refactor-current-changes) | |||
| (defvar elpy-refactor-window-configuration nil | |||
| "The old window configuration. Will be restored after commit.") | |||
| (make-variable-buffer-local 'elpy-refactor-window-configuration) | |||
| (make-obsolete | |||
| 'elpy-refactor | |||
| "Refactoring has been unstable and flakey, support will be dropped in the future." | |||
| "elpy 1.5.0") | |||
| (defun elpy-refactor () | |||
| "Run the Elpy refactoring interface for Python code." | |||
| (interactive) | |||
| (save-some-buffers) | |||
| (let* ((selection (elpy-refactor-select | |||
| (elpy-refactor-rpc-get-options))) | |||
| (method (car selection)) | |||
| (args (cdr selection))) | |||
| (when method | |||
| (elpy-refactor-create-change-buffer | |||
| (elpy-refactor-rpc-get-changes method args))))) | |||
| (defun elpy-refactor-select (options) | |||
| "Show the user the refactoring options and let her choose one. | |||
| Depending on the chosen option, ask the user for further | |||
| arguments and build the argument. | |||
| Return a cons cell of the name of the option and the arg list | |||
| created." | |||
| (let ((buf (get-buffer-create "*Elpy Refactor*")) | |||
| (pos (vector (1- (point)) | |||
| (ignore-errors | |||
| (1- (region-beginning))) | |||
| (ignore-errors | |||
| (1- (region-end))))) | |||
| (inhibit-read-only t) | |||
| (options (sort options | |||
| (lambda (a b) | |||
| (let ((cata (cdr (assq 'category a))) | |||
| (catb (cdr (assq 'category b)))) | |||
| (if (equal cata catb) | |||
| (string< (cdr (assq 'description a)) | |||
| (cdr (assq 'description b))) | |||
| (string< cata catb)))))) | |||
| (key ?a) | |||
| last-category | |||
| option-alist) | |||
| (with-current-buffer buf | |||
| (erase-buffer) | |||
| (dolist (option options) | |||
| (let ((category (cdr (assq 'category option))) | |||
| (description (cdr (assq 'description option))) | |||
| (name (cdr (assq 'name option))) | |||
| (args (cdr (assq 'args option)))) | |||
| (when (not (equal category last-category)) | |||
| (when last-category | |||
| (insert "\n")) | |||
| (insert (propertize category 'face 'bold) "\n") | |||
| (setq last-category category)) | |||
| (insert " (" key ") " description "\n") | |||
| (setq option-alist (cons (list key name args) | |||
| option-alist)) | |||
| (setq key (1+ key)))) | |||
| (let ((window-conf (current-window-configuration))) | |||
| (unwind-protect | |||
| (progn | |||
| (with-selected-window (display-buffer buf) | |||
| (goto-char (point-min))) | |||
| (fit-window-to-buffer (get-buffer-window buf)) | |||
| (let* ((key (read-key "Refactoring action? ")) | |||
| (entry (cdr (assoc key option-alist)))) | |||
| (kill-buffer buf) | |||
| (cons (car entry) ; name | |||
| (elpy-refactor-build-arguments (cadr entry) | |||
| pos)))) | |||
| (set-window-configuration window-conf)))))) | |||
| (defun elpy-refactor-build-arguments (args pos) | |||
| "Translate an argument list specification to an argument list. | |||
| POS is a vector of three elements, the current offset, the offset | |||
| of the beginning of the region, and the offset of the end of the | |||
| region. | |||
| ARGS is a list of triples, each triple containing the name of an | |||
| argument (ignored), the type of the argument, and a possible | |||
| prompt string. | |||
| Available types: | |||
| offset - The offset in the buffer, (1- (point)) | |||
| start_offset - Offset of the beginning of the region | |||
| end_offset - Offset of the end of the region | |||
| string - A free-form string | |||
| filename - A non-existing file name | |||
| directory - An existing directory name | |||
| boolean - A boolean question" | |||
| (mapcar (lambda (arg) | |||
| (let ((type (cadr arg)) | |||
| (prompt (caddr arg))) | |||
| (cond | |||
| ((equal type "offset") | |||
| (aref pos 0)) | |||
| ((equal type "start_offset") | |||
| (aref pos 1)) | |||
| ((equal type "end_offset") | |||
| (aref pos 2)) | |||
| ((equal type "string") | |||
| (read-from-minibuffer prompt)) | |||
| ((equal type "filename") | |||
| (expand-file-name | |||
| (read-file-name prompt))) | |||
| ((equal type "directory") | |||
| (expand-file-name | |||
| (read-directory-name prompt))) | |||
| ((equal type "boolean") | |||
| (y-or-n-p prompt))))) | |||
| args)) | |||
| (defun elpy-refactor-create-change-buffer (changes) | |||
| "Show the user a buffer of changes. | |||
| The user can review the changes and confirm them with | |||
| \\[elpy-refactor-commit]." | |||
| (when (not changes) | |||
| (error "No changes for this refactoring action.")) | |||
| (with-current-buffer (get-buffer-create "*Elpy Refactor*") | |||
| (elpy-refactor-mode) | |||
| (setq elpy-refactor-changes changes | |||
| elpy-refactor-window-configuration (current-window-configuration)) | |||
| (let ((inhibit-read-only t)) | |||
| (erase-buffer) | |||
| (elpy-refactor-insert-changes changes)) | |||
| (select-window (display-buffer (current-buffer))) | |||
| (goto-char (point-min)))) | |||
| (defun elpy-refactor-insert-changes (changes) | |||
| "Format and display the changes described in CHANGES." | |||
| (insert (propertize "Use C-c C-c to apply the following changes." | |||
| 'face 'bold) | |||
| "\n\n") | |||
| (dolist (change changes) | |||
| (let ((action (cdr (assq 'action change)))) | |||
| (cond | |||
| ((equal action "change") | |||
| (insert (cdr (assq 'diff change)) | |||
| "\n")) | |||
| ((equal action "create") | |||
| (let ((type (cdr (assq 'type change)))) | |||
| (if (equal type "file") | |||
| (insert "+++ " (cdr (assq 'file change)) "\n" | |||
| "Create file " (cdr (assq 'file change)) "\n" | |||
| "\n") | |||
| (insert "+++ " (cdr (assq 'path change)) "\n" | |||
| "Create directory " (cdr (assq 'path change)) "\n" | |||
| "\n")))) | |||
| ((equal action "move") | |||
| (insert "--- " (cdr (assq 'source change)) "\n" | |||
| "+++ " (cdr (assq 'destination change)) "\n" | |||
| "Rename " (cdr (assq 'type change)) "\n" | |||
| "\n")) | |||
| ((equal action "delete") | |||
| (let ((type (cdr (assq 'type change)))) | |||
| (if (equal type "file") | |||
| (insert "--- " (cdr (assq 'file change)) "\n" | |||
| "Delete file " (cdr (assq 'file change)) "\n" | |||
| "\n") | |||
| (insert "--- " (cdr (assq 'path change)) "\n" | |||
| "Delete directory " (cdr (assq 'path change)) "\n" | |||
| "\n")))))))) | |||
| (defvar elpy-refactor-mode-map | |||
| (let ((map (make-sparse-keymap))) | |||
| (define-key map (kbd "C-c C-c") 'elpy-refactor-commit) | |||
| (define-key map (kbd "q") 'bury-buffer) | |||
| (define-key map (kbd "h") 'describe-mode) | |||
| (define-key map (kbd "?") 'describe-mode) | |||
| map) | |||
| "The key map for `elpy-refactor-mode'.") | |||
| (define-derived-mode elpy-refactor-mode diff-mode "Elpy Refactor" | |||
| "Mode to display refactoring actions and ask confirmation from the user. | |||
| \\{elpy-refactor-mode-map}" | |||
| :group 'elpy | |||
| (view-mode 1)) | |||
| (defun elpy-refactor-commit () | |||
| "Commit the changes in the current buffer." | |||
| (interactive) | |||
| (when (not elpy-refactor-changes) | |||
| (error "No changes to commit.")) | |||
| ;; Restore the window configuration as the first thing so that | |||
| ;; changes below are visible to the user. Especially the point | |||
| ;; change in possible buffer changes. | |||
| (set-window-configuration elpy-refactor-window-configuration) | |||
| (dolist (change elpy-refactor-changes) | |||
| (let ((action (cdr (assq 'action change)))) | |||
| (cond | |||
| ((equal action "change") | |||
| (with-current-buffer (find-file-noselect (cdr (assq 'file change))) | |||
| ;; This would break for save-excursion as the buffer is | |||
| ;; truncated, so all markets now point to position 1. | |||
| (let ((old-point (point))) | |||
| (undo-boundary) | |||
| (erase-buffer) | |||
| (insert (cdr (assq 'contents change))) | |||
| (undo-boundary) | |||
| (goto-char old-point)))) | |||
| ((equal action "create") | |||
| (if (equal (cdr (assq 'type change)) | |||
| "file") | |||
| (find-file-noselect (cdr (assq 'file change))) | |||
| (make-directory (cdr (assq 'path change))))) | |||
| ((equal action "move") | |||
| (let* ((source (cdr (assq 'source change))) | |||
| (dest (cdr (assq 'destination change))) | |||
| (buf (get-file-buffer source))) | |||
| (when buf | |||
| (with-current-buffer buf | |||
| (setq buffer-file-name dest) | |||
| (rename-buffer (file-name-nondirectory dest) t))) | |||
| (rename-file source dest))) | |||
| ((equal action "delete") | |||
| (if (equal (cdr (assq 'type change)) "file") | |||
| (let ((name (cdr (assq 'file change)))) | |||
| (when (y-or-n-p (format "Really delete %s? " name)) | |||
| (delete-file name t))) | |||
| (let ((name (cdr (assq 'directory change)))) | |||
| (when (y-or-n-p (format "Really delete %s? " name)) | |||
| (delete-directory name nil t)))))))) | |||
| (kill-buffer (current-buffer))) | |||
| (defun elpy-refactor-rpc-get-options () | |||
| "Get a list of refactoring options from the Elpy RPC." | |||
| (if (use-region-p) | |||
| (elpy-rpc "get_refactor_options" | |||
| (list (buffer-file-name) | |||
| (1- (region-beginning)) | |||
| (1- (region-end)))) | |||
| (elpy-rpc "get_refactor_options" | |||
| (list (buffer-file-name) | |||
| (1- (point)))))) | |||
| (defun elpy-refactor-rpc-get-changes (method args) | |||
| "Get a list of changes from the Elpy RPC after applying METHOD with ARGS." | |||
| (elpy-rpc "refactor" | |||
| (list (buffer-file-name) | |||
| method args))) | |||
| (provide 'elpy-refactor) | |||
| ;;; elpy-refactor.el ends here | |||
| @ -0,0 +1,41 @@ | |||
| # Elpy, the Emacs Lisp Python Environment | |||
| # Copyright (C) 2013 Jorgen Schaefer | |||
| # Author: Jorgen Schaefer <contact@jorgenschaefer.de> | |||
| # URL: http://github.com/jorgenschaefer/elpy | |||
| # 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/>. | |||
| """The Emacs Lisp Python Environment. | |||
| Elpy is a mode for Emacs to support writing Python code. This package | |||
| provides the backend within Python to support auto-completion, | |||
| documentation extraction, and navigation. | |||
| Emacs will start the protocol by running the module itself, like so: | |||
| python -m elpy | |||
| This will emit a greeting string on a single line, and then wait for | |||
| the protocol to start. Details of the protocol can be found in | |||
| elpy.rpc. | |||
| This package is unlikely to be useful on its own. | |||
| """ | |||
| __author__ = "Jorgen Schaefer" | |||
| __version__ = "1.7.1" | |||
| __license__ = "GPL" | |||
| @ -0,0 +1,26 @@ | |||
| """Main interface to the RPC server. | |||
| You should be able to just run the following to use this module: | |||
| python -m elpy | |||
| The first line should be "elpy-rpc ready". If it isn't, something | |||
| broke. | |||
| """ | |||
| import sys | |||
| import elpy | |||
| from elpy.server import ElpyRPCServer | |||
| if __name__ == '__main__': | |||
| # Workaround for libraries writing to stderr while they should | |||
| # not. See #458. Can be removed once #461 is implemented. | |||
| import os | |||
| sys.stderr = open(os.devnull) | |||
| # End workaround | |||
| sys.stdout.write('elpy-rpc ready ({0})\n' | |||
| .format(elpy.__version__)) | |||
| sys.stdout.flush() | |||
| ElpyRPCServer().serve_forever() | |||
| @ -0,0 +1,33 @@ | |||
| """Python 2/3 compatibility definitions. | |||
| These are used by the rest of Elpy to keep compatibility definitions | |||
| in one place. | |||
| """ | |||
| import sys | |||
| if sys.version_info >= (3, 0): | |||
| PYTHON3 = True | |||
| from io import StringIO | |||
| def ensure_not_unicode(obj): | |||
| return obj | |||
| else: | |||
| PYTHON3 = False | |||
| from StringIO import StringIO # noqa | |||
| def ensure_not_unicode(obj): | |||
| """Return obj. If it's a unicode string, convert it to str first. | |||
| Pydoc functions simply don't find anything for unicode | |||
| strings. No idea why. | |||
| """ | |||
| if isinstance(obj, unicode): | |||
| return obj.encode("utf-8") | |||
| else: | |||
| return obj | |||
| @ -0,0 +1,82 @@ | |||
| """Glue for the "importmagic" library. | |||
| """ | |||
| import os | |||
| import sys | |||
| import threading | |||
| try: | |||
| import importmagic.index | |||
| import importmagic.symbols | |||
| import importmagic.importer | |||
| except ImportError: # pragma: no cover | |||
| importmagic = None | |||
| class ImportMagic(object): | |||
| def __init__(self): | |||
| self.is_enabled = bool(importmagic) | |||
| # fail_message is reported to the user when symbol_index | |||
| # is (still) None | |||
| self.fail_message = "symbol index is not yet ready" | |||
| self.project_root = None | |||
| self.symbol_index = None | |||
| self._thread = None | |||
| def _build_symbol_index(self, project_root, custom_path, blacklist_re): | |||
| try: | |||
| index = importmagic.index.SymbolIndex(blacklist_re=blacklist_re) | |||
| if os.environ.get('ELPY_TEST'): | |||
| # test suite support: do not index the whole PYTHONPATH, it | |||
| # takes much too long | |||
| index.build_index([]) | |||
| elif custom_path: | |||
| index.build_index(custom_path) | |||
| else: | |||
| index.build_index([project_root] + sys.path) | |||
| except Exception as e: | |||
| self.fail_message = "symbol index failed to build: %s" % e | |||
| else: | |||
| self.symbol_index = index | |||
| def build_index(self, project_root, custom_path=None, blacklist_re=None): | |||
| self.project_root = None | |||
| self._thread = threading.Thread(target=self._build_symbol_index, | |||
| args=(project_root, custom_path, | |||
| blacklist_re)) | |||
| self._thread.setDaemon(True) | |||
| self._thread.start() | |||
| def get_import_symbols(self, symbol): | |||
| scores = self.symbol_index.symbol_scores(symbol) | |||
| return ["from %s import %s" % (mod, var) if var else "import %s" % mod | |||
| for (_, mod, var) in scores] | |||
| def add_import(self, source, statement): | |||
| imports = importmagic.importer.Imports(self.symbol_index, source) | |||
| if statement.startswith('import '): | |||
| imports.add_import(statement[7:]) | |||
| else: | |||
| sep = statement.find(' import ') | |||
| if sep > -1: | |||
| imports.add_import_from(statement[5:sep], statement[sep+8:]) | |||
| start_line, end_line, import_block = imports.get_update() | |||
| return start_line, end_line, import_block | |||
| def get_unresolved_symbols(self, source): | |||
| scope = importmagic.symbols.Scope.from_source(source) | |||
| unres, unref = scope.find_unresolved_and_unreferenced_symbols() | |||
| return list(unres) | |||
| def remove_unreferenced_imports(self, source): | |||
| scope = importmagic.symbols.Scope.from_source(source) | |||
| unres, unref = scope.find_unresolved_and_unreferenced_symbols() | |||
| # Note: we do not supply "unres" to the call below, since we do | |||
| # not want to add imports without querying the user from which | |||
| # module symbols should be imported. | |||
| start_line, end_line, import_block = importmagic.importer.get_update( | |||
| source, self.symbol_index, set(), unref) | |||
| return start_line, end_line, import_block | |||
| @ -0,0 +1,320 @@ | |||
| """Elpy backend using the Jedi library. | |||
| This backend uses the Jedi library: | |||
| https://github.com/davidhalter/jedi | |||
| """ | |||
| import sys | |||
| import traceback | |||
| import jedi | |||
| from elpy import rpc | |||
| class JediBackend(object): | |||
| """The Jedi backend class. | |||
| Implements the RPC calls we can pass on to Jedi. | |||
| Documentation: http://jedi.jedidjah.ch/en/latest/docs/plugin-api.html | |||
| """ | |||
| name = "jedi" | |||
| def __init__(self, project_root): | |||
| self.project_root = project_root | |||
| self.completions = {} | |||
| sys.path.append(project_root) | |||
| def rpc_get_completions(self, filename, source, offset): | |||
| line, column = pos_to_linecol(source, offset) | |||
| proposals = run_with_debug(jedi, 'completions', | |||
| source=source, line=line, column=column, | |||
| path=filename, encoding='utf-8') | |||
| if proposals is None: | |||
| return [] | |||
| self.completions = dict((proposal.name, proposal) | |||
| for proposal in proposals) | |||
| return [{'name': proposal.name, | |||
| 'suffix': proposal.complete, | |||
| 'annotation': proposal.type, | |||
| 'meta': proposal.description} | |||
| for proposal in proposals] | |||
| def rpc_get_completion_docstring(self, completion): | |||
| proposal = self.completions.get(completion) | |||
| if proposal is None: | |||
| return None | |||
| else: | |||
| return proposal.docstring(fast=False) | |||
| def rpc_get_completion_location(self, completion): | |||
| proposal = self.completions.get(completion) | |||
| if proposal is None: | |||
| return None | |||
| else: | |||
| return (proposal.module_path, proposal.line) | |||
| def rpc_get_docstring(self, filename, source, offset): | |||
| line, column = pos_to_linecol(source, offset) | |||
| try: | |||
| locations = run_with_debug(jedi, 'goto_definitions', | |||
| source=source, line=line, column=column, | |||
| path=filename, encoding='utf-8', | |||
| re_raise=jedi.NotFoundError) | |||
| except jedi.NotFoundError: | |||
| return None | |||
| if locations: | |||
| return locations[-1].docstring() | |||
| else: | |||
| return None | |||
| def rpc_get_definition(self, filename, source, offset): | |||
| line, column = pos_to_linecol(source, offset) | |||
| try: | |||
| locations = run_with_debug(jedi, 'goto_definitions', | |||
| source=source, line=line, column=column, | |||
| path=filename, encoding='utf-8', | |||
| re_raise=jedi.NotFoundError) | |||
| except jedi.NotFoundError: | |||
| return None | |||
| # goto_definitions() can return silly stuff like __builtin__ | |||
| # for int variables, so we fall back on goto() in those | |||
| # cases. See issue #76. | |||
| if ( | |||
| locations and | |||
| locations[0].module_path is None | |||
| ): | |||
| locations = run_with_debug(jedi, 'goto_assignments', | |||
| source=source, line=line, | |||
| column=column, | |||
| path=filename, encoding='utf-8') | |||
| if not locations: | |||
| return None | |||
| else: | |||
| loc = locations[-1] | |||
| try: | |||
| if loc.module_path: | |||
| if loc.module_path == filename: | |||
| offset = linecol_to_pos(source, | |||
| loc.line, | |||
| loc.column) | |||
| else: | |||
| with open(loc.module_path) as f: | |||
| offset = linecol_to_pos(f.read(), | |||
| loc.line, | |||
| loc.column) | |||
| except IOError: | |||
| return None | |||
| return (loc.module_path, offset) | |||
| def rpc_get_calltip(self, filename, source, offset): | |||
| line, column = pos_to_linecol(source, offset) | |||
| calls = run_with_debug(jedi, 'call_signatures', | |||
| source=source, line=line, column=column, | |||
| path=filename, encoding='utf-8') | |||
| if calls: | |||
| call = calls[0] | |||
| else: | |||
| call = None | |||
| if not call: | |||
| return None | |||
| return {"name": call.name, | |||
| "index": call.index, | |||
| "params": [param.description for param in call.params]} | |||
| def rpc_get_usages(self, filename, source, offset): | |||
| """Return the uses of the symbol at offset. | |||
| Returns a list of occurrences of the symbol, as dicts with the | |||
| fields name, filename, and offset. | |||
| """ | |||
| line, column = pos_to_linecol(source, offset) | |||
| try: | |||
| uses = run_with_debug(jedi, 'usages', | |||
| source=source, line=line, column=column, | |||
| path=filename, encoding='utf-8', | |||
| re_raise=(jedi.NotFoundError,)) | |||
| except jedi.NotFoundError: | |||
| return [] | |||
| if uses is None: | |||
| return None | |||
| result = [] | |||
| for use in uses: | |||
| if use.module_path == filename: | |||
| offset = linecol_to_pos(source, use.line, use.column) | |||
| elif use.module_path is not None: | |||
| with open(use.module_path) as f: | |||
| text = f.read() | |||
| offset = linecol_to_pos(text, use.line, use.column) | |||
| result.append({"name": use.name, | |||
| "filename": use.module_path, | |||
| "offset": offset}) | |||
| return result | |||
| # From the Jedi documentation: | |||
| # | |||
| # line is the current line you want to perform actions on (starting | |||
| # with line #1 as the first line). column represents the current | |||
| # column/indent of the cursor (starting with zero). source_path | |||
| # should be the path of your file in the file system. | |||
| def pos_to_linecol(text, pos): | |||
| """Return a tuple of line and column for offset pos in text. | |||
| Lines are one-based, columns zero-based. | |||
| This is how Jedi wants it. Don't ask me why. | |||
| """ | |||
| line_start = text.rfind("\n", 0, pos) + 1 | |||
| line = text.count("\n", 0, line_start) + 1 | |||
| col = pos - line_start | |||
| return line, col | |||
| def linecol_to_pos(text, line, col): | |||
| """Return the offset of this line and column in text. | |||
| Lines are one-based, columns zero-based. | |||
| This is how Jedi wants it. Don't ask me why. | |||
| """ | |||
| nth_newline_offset = 0 | |||
| for i in range(line - 1): | |||
| new_offset = text.find("\n", nth_newline_offset) | |||
| if new_offset < 0: | |||
| raise ValueError("Text does not have {0} lines." | |||
| .format(line)) | |||
| nth_newline_offset = new_offset + 1 | |||
| offset = nth_newline_offset + col | |||
| if offset > len(text): | |||
| raise ValueError("Line {0} column {1} is not within the text" | |||
| .format(line, col)) | |||
| return offset | |||
| def run_with_debug(jedi, name, *args, **kwargs): | |||
| re_raise = kwargs.pop('re_raise', ()) | |||
| # Remove form feed characters, they confuse Jedi (jedi#424) | |||
| if 'source' in kwargs: | |||
| kwargs['source'] = kwargs['source'].replace("\f", " ") | |||
| try: | |||
| script = jedi.Script(*args, **kwargs) | |||
| return getattr(script, name)() | |||
| except Exception as e: | |||
| if isinstance(e, re_raise): | |||
| raise | |||
| # Bug jedi#417 | |||
| if isinstance(e, TypeError) and str(e) == 'no dicts allowed': | |||
| return None | |||
| # Bug jedi#427 | |||
| if isinstance(e, UnicodeDecodeError): | |||
| return None | |||
| # Bug jedi#429 | |||
| if isinstance(e, IndexError): | |||
| return None | |||
| # Bug jedi#431 | |||
| if isinstance(e, AttributeError) and str(e).endswith("'end_pos'"): | |||
| return None | |||
| # Bug in Python 2.6, see #275 | |||
| if isinstance(e, OSError) and e.errno == 13: | |||
| return None | |||
| # Bug jedi#466 | |||
| if ( | |||
| isinstance(e, SyntaxError) and | |||
| "EOL while scanning string literal" in str(e) | |||
| ): | |||
| return None | |||
| # Bug jedi#482 | |||
| if isinstance(e, UnicodeEncodeError): | |||
| return None | |||
| # Bug jedi#485 | |||
| if ( | |||
| isinstance(e, ValueError) and | |||
| "invalid \\x escape" in str(e) | |||
| ): | |||
| return None | |||
| # Bug jedi#485 in Python 3 | |||
| if ( | |||
| isinstance(e, SyntaxError) and | |||
| "truncated \\xXX escape" in str(e) | |||
| ): | |||
| return None | |||
| # Bug jedi#465 | |||
| if ( | |||
| isinstance(e, SyntaxError) and | |||
| "encoding declaration in Unicode string" in str(e) | |||
| ): | |||
| return None | |||
| # Bug #337 / jedi#471 | |||
| if ( | |||
| isinstance(e, ImportError) and | |||
| "No module named" in str(e) | |||
| ): | |||
| return None | |||
| # Bug #365 / jedi#486 - fixed in Jedi 0.8.2 | |||
| if ( | |||
| isinstance(e, UnboundLocalError) and | |||
| "local variable 'path' referenced before assignment" in str(e) | |||
| ): | |||
| return None | |||
| # Bug #366 / jedi#491 | |||
| if ( | |||
| isinstance(e, ValueError) and | |||
| "__loader__ is None" in str(e) | |||
| ): | |||
| return None | |||
| # Bug #353 | |||
| if ( | |||
| isinstance(e, OSError) and | |||
| "No such file or directory" in str(e) | |||
| ): | |||
| return None | |||
| from jedi import debug | |||
| debug_info = [] | |||
| def _debug(level, str_out): | |||
| if level == debug.NOTICE: | |||
| prefix = "[N]" | |||
| elif level == debug.WARNING: | |||
| prefix = "[W]" | |||
| else: | |||
| prefix = "[?]" | |||
| debug_info.append(u"{0} {1}".format(prefix, str_out)) | |||
| jedi.set_debug_function(_debug, speed=False) | |||
| try: | |||
| script = jedi.Script(*args, **kwargs) | |||
| return getattr(script, name)() | |||
| except Exception as e: | |||
| source = kwargs.get('source') | |||
| sc_args = [] | |||
| sc_args.extend(repr(arg) for arg in args) | |||
| sc_args.extend("{0}={1}".format(k, "source" if k == "source" | |||
| else repr(v)) | |||
| for (k, v) in kwargs.items()) | |||
| data = { | |||
| "traceback": traceback.format_exc(), | |||
| "jedi_debug_info": {'script_args': ", ".join(sc_args), | |||
| 'source': source, | |||
| 'method': name, | |||
| 'debug_info': debug_info} | |||
| } | |||
| raise rpc.Fault(message=str(e), | |||
| code=500, | |||
| data=data) | |||
| finally: | |||
| jedi.set_debug_function(None) | |||
| @ -0,0 +1,91 @@ | |||
| import sys | |||
| import types | |||
| from pydoc import safeimport, resolve, ErrorDuringImport | |||
| from pkgutil import iter_modules | |||
| from elpy import compat | |||
| # Types we want to recurse into (nodes). | |||
| CONTAINER_TYPES = (type, types.ModuleType) | |||
| # Types of attributes we can get documentation for (leaves). | |||
| PYDOC_TYPES = (type, | |||
| types.FunctionType, | |||
| types.BuiltinFunctionType, | |||
| types.BuiltinMethodType, | |||
| types.MethodType, | |||
| types.ModuleType) | |||
| if not compat.PYTHON3: # pragma: nocover | |||
| # Python 2 old style classes | |||
| CONTAINER_TYPES = tuple(list(CONTAINER_TYPES) + [types.ClassType]) | |||
| PYDOC_TYPES = tuple(list(PYDOC_TYPES) + [types.ClassType]) | |||
| def get_pydoc_completions(modulename): | |||
| """Get possible completions for modulename for pydoc. | |||
| Returns a list of possible values to be passed to pydoc. | |||
| """ | |||
| modulename = compat.ensure_not_unicode(modulename) | |||
| modulename = modulename.rstrip(".") | |||
| if modulename == "": | |||
| return sorted(get_modules()) | |||
| candidates = get_completions(modulename) | |||
| if candidates: | |||
| return sorted(candidates) | |||
| needle = modulename | |||
| if "." in needle: | |||
| modulename, part = needle.rsplit(".", 1) | |||
| candidates = get_completions(modulename) | |||
| else: | |||
| candidates = get_modules() | |||
| return sorted(candidate for candidate in candidates | |||
| if candidate.startswith(needle)) | |||
| def get_completions(modulename): | |||
| modules = set("{0}.{1}".format(modulename, module) | |||
| for module in get_modules(modulename)) | |||
| try: | |||
| module, name = resolve(modulename) | |||
| except ImportError: | |||
| return modules | |||
| if isinstance(module, CONTAINER_TYPES): | |||
| modules.update("{0}.{1}".format(modulename, name) | |||
| for name in dir(module) | |||
| if not name.startswith("_") and | |||
| isinstance(getattr(module, name), | |||
| PYDOC_TYPES)) | |||
| return modules | |||
| def get_modules(modulename=None): | |||
| """Return a list of modules and packages under modulename. | |||
| If modulename is not given, return a list of all top level modules | |||
| and packages. | |||
| """ | |||
| modulename = compat.ensure_not_unicode(modulename) | |||
| if not modulename: | |||
| try: | |||
| return ([modname for (importer, modname, ispkg) | |||
| in iter_modules() | |||
| if not modname.startswith("_")] + | |||
| list(sys.builtin_module_names)) | |||
| except OSError: | |||
| # Bug in Python 2.6, see #275 | |||
| return list(sys.builtin_module_names) | |||
| try: | |||
| module = safeimport(modulename) | |||
| except ErrorDuringImport: | |||
| return [] | |||
| if module is None: | |||
| return [] | |||
| if hasattr(module, "__path__"): | |||
| return [modname for (importer, modname, ispkg) | |||
| in iter_modules(module.__path__) | |||
| if not modname.startswith("_")] | |||
| return [] | |||
| @ -0,0 +1,366 @@ | |||
| """Refactoring methods for elpy. | |||
| This interfaces directly with rope, regardless of the backend used, | |||
| because the other backends don't really offer refactoring choices. | |||
| Once Jedi is similarly featureful as Rope we can try and offer both. | |||
| # Too complex: | |||
| - Restructure: Interesting, but too complex, and needs deep Rope | |||
| knowledge to do well. | |||
| - ChangeSignature: Slightly less complex interface, but still to | |||
| complex, requiring a large effort for the benefit. | |||
| # Too useless: | |||
| I could not get these to work in any useful fashion. I might be doing | |||
| something wrong. | |||
| - ExtractVariable does not replace the code extracted with the | |||
| variable, making it a glorified copy&paste function. Emacs can do | |||
| better than this interface by itself. | |||
| - EncapsulateField: Getter/setter methods are outdated, this should be | |||
| using properties. | |||
| - IntroduceFactory: Inserts a trivial method to the current class. | |||
| Cute. | |||
| - IntroduceParameter: Introduces a parameter correctly, but does not | |||
| replace the old code with the parameter. So it just edits the | |||
| argument list and adds a shiny default. | |||
| - LocalToField: Seems to just add "self." in front of all occurrences | |||
| of a variable in the local scope. | |||
| - MethodObject: This turns the current method into a callable | |||
| class/object. Not sure what that would be good for. | |||
| # Can't even get to work: | |||
| - ImportOrganizer expand_star_imports, handle_long_imports, | |||
| relatives_to_absolutes: Seem not to do anything. | |||
| - create_move: I was not able to figure out what it would like to see | |||
| as its attrib argument. | |||
| """ | |||
| from elpy.rpc import Fault | |||
| try: | |||
| from rope.base.exceptions import RefactoringError | |||
| from rope.base.project import Project | |||
| from rope.base.libutils import path_to_resource | |||
| from rope.base import change as rope_change | |||
| from rope.base import worder | |||
| from rope.refactor.importutils import ImportOrganizer | |||
| from rope.refactor.topackage import ModuleToPackage | |||
| from rope.refactor.rename import Rename | |||
| from rope.refactor.move import create_move | |||
| from rope.refactor.inline import create_inline | |||
| from rope.refactor.extract import ExtractMethod | |||
| from rope.refactor.usefunction import UseFunction | |||
| ROPE_AVAILABLE = True | |||
| except ImportError: | |||
| ROPE_AVAILABLE = False | |||
| def options(description, **kwargs): | |||
| """Decorator to set some options on a method.""" | |||
| def set_notes(function): | |||
| function.refactor_notes = {'name': function.__name__, | |||
| 'category': "Miscellaneous", | |||
| 'description': description, | |||
| 'doc': getattr(function, '__doc__', | |||
| ''), | |||
| 'args': []} | |||
| function.refactor_notes.update(kwargs) | |||
| return function | |||
| return set_notes | |||
| class Refactor(object): | |||
| """The main refactoring interface. | |||
| Once initialized, the first call should be to get_refactor_options | |||
| to get a list of refactoring options at a given position. The | |||
| returned value will also list any additional options required. | |||
| Once you picked one, you can call get_changes to get the actual | |||
| refactoring changes. | |||
| """ | |||
| def __init__(self, project_root, filename): | |||
| self.project_root = project_root | |||
| if ROPE_AVAILABLE: | |||
| self.project = Project(project_root, ropefolder=None) | |||
| self.resource = path_to_resource(self.project, filename) | |||
| else: | |||
| self.project = None | |||
| self.resource = FakeResource(filename) | |||
| def get_refactor_options(self, start, end=None): | |||
| """Return a list of options for refactoring at the given position. | |||
| If `end` is also given, refactoring on a region is assumed. | |||
| Each option is a dictionary of key/value pairs. The value of | |||
| the key 'name' is the one to be used for get_changes. | |||
| The key 'args' contains a list of additional arguments | |||
| required for get_changes. | |||
| """ | |||
| result = [] | |||
| for symbol in dir(self): | |||
| if not symbol.startswith("refactor_"): | |||
| continue | |||
| method = getattr(self, symbol) | |||
| if not method.refactor_notes.get('available', True): | |||
| continue | |||
| category = method.refactor_notes['category'] | |||
| if end is not None and category != 'Region': | |||
| continue | |||
| if end is None and category == 'Region': | |||
| continue | |||
| is_on_symbol = self._is_on_symbol(start) | |||
| if not is_on_symbol and category in ('Symbol', 'Method'): | |||
| continue | |||
| requires_import = method.refactor_notes.get('only_on_imports', | |||
| False) | |||
| if requires_import and not self._is_on_import_statement(start): | |||
| continue | |||
| result.append(method.refactor_notes) | |||
| return result | |||
| def _is_on_import_statement(self, offset): | |||
| "Does this offset point to an import statement?" | |||
| data = self.resource.read() | |||
| bol = data.rfind("\n", 0, offset) + 1 | |||
| eol = data.find("\n", 0, bol) | |||
| if eol == -1: | |||
| eol = len(data) | |||
| line = data[bol:eol] | |||
| line = line.strip() | |||
| if line.startswith("import ") or line.startswith("from "): | |||
| return True | |||
| else: | |||
| return False | |||
| def _is_on_symbol(self, offset): | |||
| "Is this offset on a symbol?" | |||
| if not ROPE_AVAILABLE: | |||
| return False | |||
| data = self.resource.read() | |||
| if offset >= len(data): | |||
| return False | |||
| if data[offset] != '_' and not data[offset].isalnum(): | |||
| return False | |||
| word = worder.get_name_at(self.resource, offset) | |||
| if word: | |||
| return True | |||
| else: | |||
| return False | |||
| def get_changes(self, name, *args): | |||
| """Return a list of changes for the named refactoring action. | |||
| Changes are dictionaries describing a single action to be | |||
| taken for the refactoring to be successful. | |||
| A change has an action and possibly a type. In the description | |||
| below, the action is before the slash and the type after it. | |||
| change: Change file contents | |||
| - file: The path to the file to change | |||
| - contents: The new contents for the file | |||
| - Diff: A unified diff showing the changes introduced | |||
| create/file: Create a new file | |||
| - file: The file to create | |||
| create/directory: Create a new directory | |||
| - path: The directory to create | |||
| move/file: Rename a file | |||
| - source: The path to the source file | |||
| - destination: The path to the destination file name | |||
| move/directory: Rename a directory | |||
| - source: The path to the source directory | |||
| - destination: The path to the destination directory name | |||
| delete/file: Delete a file | |||
| - file: The file to delete | |||
| delete/directory: Delete a directory | |||
| - path: The directory to delete | |||
| """ | |||
| if not name.startswith("refactor_"): | |||
| raise ValueError("Bad refactoring name {0}".format(name)) | |||
| method = getattr(self, name) | |||
| if not method.refactor_notes.get('available', True): | |||
| raise RuntimeError("Method not available") | |||
| return method(*args) | |||
| @options("Convert from x import y to import x.y as y", category="Imports", | |||
| args=[("offset", "offset", None)], | |||
| only_on_imports=True, | |||
| available=ROPE_AVAILABLE) | |||
| def refactor_froms_to_imports(self, offset): | |||
| """Converting imports of the form "from ..." to "import ...".""" | |||
| refactor = ImportOrganizer(self.project) | |||
| changes = refactor.froms_to_imports(self.resource, offset) | |||
| return translate_changes(changes) | |||
| @options("Reorganize and clean up", category="Imports", | |||
| available=ROPE_AVAILABLE) | |||
| def refactor_organize_imports(self): | |||
| """Clean up and organize imports.""" | |||
| refactor = ImportOrganizer(self.project) | |||
| changes = refactor.organize_imports(self.resource) | |||
| return translate_changes(changes) | |||
| @options("Convert the current module into a package", category="Module", | |||
| available=ROPE_AVAILABLE) | |||
| def refactor_module_to_package(self): | |||
| """Convert the current module into a package.""" | |||
| refactor = ModuleToPackage(self.project, self.resource) | |||
| changes = refactor.get_changes() | |||
| return translate_changes(changes) | |||
| @options("Rename symbol at point", category="Symbol", | |||
| args=[("offset", "offset", None), | |||
| ("new_name", "string", "Rename to: "), | |||
| ("in_hierarchy", "boolean", | |||
| "Rename in super-/subclasses as well? "), | |||
| ("docs", "boolean", | |||
| "Replace occurences in docs and strings? ") | |||
| ], | |||
| available=ROPE_AVAILABLE) | |||
| def refactor_rename_at_point(self, offset, new_name, in_hierarchy, docs): | |||
| """Rename the symbol at point.""" | |||
| try: | |||
| refactor = Rename(self.project, self.resource, offset) | |||
| except RefactoringError as e: | |||
| raise Fault(str(e), code=400) | |||
| changes = refactor.get_changes(new_name, in_hierarchy=in_hierarchy, | |||
| docs=docs) | |||
| return translate_changes(changes) | |||
| @options("Rename current module", category="Module", | |||
| args=[("new_name", "string", "Rename to: ")], | |||
| available=ROPE_AVAILABLE) | |||
| def refactor_rename_current_module(self, new_name): | |||
| """Rename the current module.""" | |||
| refactor = Rename(self.project, self.resource, None) | |||
| changes = refactor.get_changes(new_name) | |||
| return translate_changes(changes) | |||
| @options("Move the current module to a different package", | |||
| category="Module", | |||
| args=[("new_name", "directory", "Destination package: ")], | |||
| available=ROPE_AVAILABLE) | |||
| def refactor_move_module(self, new_name): | |||
| """Move the current module.""" | |||
| refactor = create_move(self.project, self.resource) | |||
| resource = path_to_resource(self.project, new_name) | |||
| changes = refactor.get_changes(resource) | |||
| return translate_changes(changes) | |||
| @options("Inline function call at point", category="Symbol", | |||
| args=[("offset", "offset", None), | |||
| ("only_this", "boolean", "Only this occurrence? ")], | |||
| available=ROPE_AVAILABLE) | |||
| def refactor_create_inline(self, offset, only_this): | |||
| """Inline the function call at point.""" | |||
| refactor = create_inline(self.project, self.resource, offset) | |||
| if only_this: | |||
| changes = refactor.get_changes(remove=False, only_current=True) | |||
| else: | |||
| changes = refactor.get_changes(remove=True, only_current=False) | |||
| return translate_changes(changes) | |||
| @options("Extract current region as a method", category="Region", | |||
| args=[("start", "start_offset", None), | |||
| ("end", "end_offset", None), | |||
| ("name", "string", "Method name: "), | |||
| ("make_global", "boolean", "Create global method? ")], | |||
| available=ROPE_AVAILABLE) | |||
| def refactor_extract_method(self, start, end, name, | |||
| make_global): | |||
| """Extract region as a method.""" | |||
| refactor = ExtractMethod(self.project, self.resource, start, end) | |||
| changes = refactor.get_changes(name, similar=True, global_=make_global) | |||
| return translate_changes(changes) | |||
| @options("Use the function at point wherever possible", category="Method", | |||
| args=[("offset", "offset", None)], | |||
| available=ROPE_AVAILABLE) | |||
| def refactor_use_function(self, offset): | |||
| """Use the function at point wherever possible.""" | |||
| refactor = UseFunction(self.project, self.resource, offset) | |||
| changes = refactor.get_changes() | |||
| return translate_changes(changes) | |||
| def translate_changes(initial_change): | |||
| """Translate rope.base.change.Change instances to dictionaries. | |||
| See Refactor.get_changes for an explanation of the resulting | |||
| dictionary. | |||
| """ | |||
| agenda = [initial_change] | |||
| result = [] | |||
| while agenda: | |||
| change = agenda.pop(0) | |||
| if isinstance(change, rope_change.ChangeSet): | |||
| agenda.extend(change.changes) | |||
| elif isinstance(change, rope_change.ChangeContents): | |||
| result.append({'action': 'change', | |||
| 'file': change.resource.real_path, | |||
| 'contents': change.new_contents, | |||
| 'diff': change.get_description()}) | |||
| elif isinstance(change, rope_change.CreateFile): | |||
| result.append({'action': 'create', | |||
| 'type': 'file', | |||
| 'file': change.resource.real_path}) | |||
| elif isinstance(change, rope_change.CreateFolder): | |||
| result.append({'action': 'create', | |||
| 'type': 'directory', | |||
| 'path': change.resource.real_path}) | |||
| elif isinstance(change, rope_change.MoveResource): | |||
| result.append({'action': 'move', | |||
| 'type': ('directory' | |||
| if change.new_resource.is_folder() | |||
| else 'file'), | |||
| 'source': change.resource.real_path, | |||
| 'destination': change.new_resource.real_path}) | |||
| elif isinstance(change, rope_change.RemoveResource): | |||
| if change.resource.is_folder(): | |||
| result.append({'action': 'delete', | |||
| 'type': 'directory', | |||
| 'path': change.resource.real_path}) | |||
| else: | |||
| result.append({'action': 'delete', | |||
| 'type': 'file', | |||
| 'file': change.resource.real_path}) | |||
| return result | |||
| class FakeResource(object): | |||
| """A fake resource in case Rope is absence.""" | |||
| def __init__(self, filename): | |||
| self.real_path = filename | |||
| def read(self): | |||
| with open(self.real_path) as f: | |||
| return f.read() | |||
| @ -0,0 +1,322 @@ | |||
| """Elpy backend using the Rope library. | |||
| This backend uses the Rope library: | |||
| http://rope.sourceforge.net/ | |||
| """ | |||
| import os | |||
| import time | |||
| import traceback | |||
| import rope.contrib.codeassist | |||
| import rope.base.project | |||
| import rope.base.libutils | |||
| import rope.base.exceptions | |||
| import rope.contrib.findit | |||
| from elpy import rpc | |||
| import elpy.pydocutils | |||
| VALIDATE_EVERY_SECONDS = 5 | |||
| MAXFIXES = 5 | |||
| class RopeBackend(object): | |||
| """The Rope backend class. | |||
| Implements the RPC calls we can pass on to Rope. Also subclasses | |||
| the native backend to provide methods Rope does not provide, if | |||
| any. | |||
| """ | |||
| name = "rope" | |||
| def __init__(self, project_root): | |||
| super(RopeBackend, self).__init__() | |||
| self.last_validation = 0 | |||
| if not os.path.exists(project_root): | |||
| project_root = "" | |||
| self.project_root = project_root | |||
| self.completions = {} | |||
| prefs = dict(ignored_resources=['*.pyc', '*~', '.ropeproject', | |||
| '.hg', '.svn', '_svn', '.git'], | |||
| python_files=['*.py'], | |||
| save_objectdb=False, | |||
| compress_objectdb=False, | |||
| automatic_soa=True, | |||
| soa_followed_calls=0, | |||
| perform_doa=True, | |||
| validate_objectdb=True, | |||
| max_history_items=32, | |||
| save_history=False, | |||
| compress_history=False, | |||
| indent_size=4, | |||
| extension_modules=[], | |||
| import_dynload_stdmods=True, | |||
| ignore_syntax_errors=False, | |||
| ignore_bad_imports=False) | |||
| self.project = rope.base.project.Project(self.project_root, | |||
| ropefolder=None, | |||
| **prefs) | |||
| def get_resource(self, filename): | |||
| if filename is not None and os.path.exists(filename): | |||
| return rope.base.libutils.path_to_resource(self.project, | |||
| filename, | |||
| 'file') | |||
| else: | |||
| return None | |||
| def validate(self): | |||
| """Validate the stored project. | |||
| This should be called before every use of Rope. It will | |||
| revalidate the project, but do some call throttling. | |||
| """ | |||
| now = time.time() | |||
| if now > self.last_validation + VALIDATE_EVERY_SECONDS: | |||
| try: | |||
| self.project.validate() | |||
| except rope.base.exceptions.ResourceNotFoundError: | |||
| pass | |||
| self.last_validation = now | |||
| def call_rope(self, rope_function, filename, source, offset, | |||
| **kwargs): | |||
| self.validate() | |||
| resource = self.get_resource(filename) | |||
| try: | |||
| return rope_function(self.project, | |||
| source, offset, | |||
| resource, | |||
| maxfixes=MAXFIXES, | |||
| **kwargs) | |||
| except (rope.base.exceptions.BadIdentifierError, | |||
| rope.base.exceptions.ModuleSyntaxError, | |||
| rope.base.exceptions.ResourceNotFoundError, | |||
| IndentationError, | |||
| LookupError, | |||
| AttributeError): | |||
| return None | |||
| except Exception as e: | |||
| data = { | |||
| "traceback": traceback.format_exc(), | |||
| "rope_debug_info": { | |||
| "project_root": self.project_root, | |||
| "filename": filename, | |||
| "source": source, | |||
| "function_name": (rope_function.__module__ + | |||
| "." + | |||
| rope_function.__name__), | |||
| "function_args": ", ".join([ | |||
| "project", "source", str(offset), "resource", | |||
| "maxfixes={0}".format(MAXFIXES) | |||
| ] + [ | |||
| u"{}={}".format(k, v) | |||
| for (k, v) in kwargs.items() | |||
| ]) | |||
| } | |||
| } | |||
| raise rpc.Fault( | |||
| code=500, | |||
| message=str(e), | |||
| data=data | |||
| ) | |||
| def rpc_get_completions(self, filename, source, offset): | |||
| proposals = self.call_rope( | |||
| rope.contrib.codeassist.code_assist, | |||
| filename, source, offset | |||
| ) | |||
| if proposals is None: | |||
| return [] | |||
| try: | |||
| starting_offset = rope.contrib.codeassist.starting_offset(source, | |||
| offset) | |||
| except (rope.base.exceptions.BadIdentifierError, | |||
| rope.base.exceptions.ModuleSyntaxError, | |||
| IndentationError, | |||
| LookupError, | |||
| AttributeError): | |||
| return [] | |||
| prefixlen = offset - starting_offset | |||
| self.completions = dict((proposal.name, proposal) | |||
| for proposal in proposals) | |||
| try: | |||
| return [{'name': proposal.name, | |||
| 'suffix': proposal.name[prefixlen:], | |||
| 'annotation': proposal.type, | |||
| 'meta': str(proposal)} | |||
| for proposal in proposals] | |||
| except rope.base.exceptions.ModuleSyntaxError: | |||
| # Bug#406 | |||
| return [] | |||
| def rpc_get_completion_docstring(self, completion): | |||
| proposal = self.completions.get(completion) | |||
| if proposal is None: | |||
| return None | |||
| else: | |||
| return proposal.get_doc() | |||
| def rpc_get_completion_location(self, completion): | |||
| proposal = self.completions.get(completion) | |||
| if proposal is None: | |||
| return None | |||
| else: | |||
| if not proposal.pyname: | |||
| return None | |||
| module, lineno = proposal.pyname.get_definition_location() | |||
| if module is None: | |||
| return None | |||
| resource = module.get_module().get_resource() | |||
| return (resource.real_path, lineno) | |||
| def rpc_get_definition(self, filename, source, offset): | |||
| location = self.call_rope( | |||
| rope.contrib.findit.find_definition, | |||
| filename, source, offset | |||
| ) | |||
| if location is None: | |||
| return None | |||
| else: | |||
| return (location.resource.real_path, location.offset) | |||
| def rpc_get_calltip(self, filename, source, offset): | |||
| offset = find_called_name_offset(source, offset) | |||
| if 0 < offset < len(source) and source[offset] == ')': | |||
| offset -= 1 | |||
| calltip = self.call_rope( | |||
| rope.contrib.codeassist.get_calltip, | |||
| filename, source, offset, | |||
| remove_self=True | |||
| ) | |||
| if calltip is None: | |||
| return None | |||
| calltip = calltip.replace(".__init__(", "(") | |||
| calltip = calltip.replace("(self)", "()") | |||
| calltip = calltip.replace("(self, ", "(") | |||
| # "elpy.tests.support.source_and_offset(source)" | |||
| # => | |||
| # "support.source_and_offset(source)" | |||
| try: | |||
| openpos = calltip.index("(") | |||
| period2 = calltip.rindex(".", 0, openpos) | |||
| period1 = calltip.rindex(".", 0, period2) | |||
| calltip = calltip[period1 + 1:] | |||
| except ValueError: | |||
| pass | |||
| return calltip | |||
| def rpc_get_docstring(self, filename, source, offset): | |||
| return self.call_rope( | |||
| rope.contrib.codeassist.get_doc, | |||
| filename, source, offset | |||
| ) | |||
| def find_called_name_offset(source, orig_offset): | |||
| """Return the offset of a calling function. | |||
| This only approximates movement. | |||
| """ | |||
| offset = min(orig_offset, len(source) - 1) | |||
| paren_count = 0 | |||
| while True: | |||
| if offset <= 1: | |||
| return orig_offset | |||
| elif source[offset] == '(': | |||
| if paren_count == 0: | |||
| return offset - 1 | |||
| else: | |||
| paren_count -= 1 | |||
| elif source[offset] == ')': | |||
| paren_count += 1 | |||
| offset -= 1 | |||
| ################################################################## | |||
| # A recurring problem in Rope for Elpy is that it searches the whole | |||
| # project root for Python files. If the user edits a file in their | |||
| # home directory, this can easily read a whole lot of files, making | |||
| # Rope practically useless. We change the file finding algorithm here | |||
| # to only recurse into directories with an __init__.py file in them. | |||
| def find_source_folders(self, folder): | |||
| for resource in folder.get_folders(): | |||
| if self._is_package(resource): | |||
| return [folder] | |||
| result = [] | |||
| for resource in folder.get_files(): | |||
| if resource.name.endswith('.py'): | |||
| result.append(folder) | |||
| break | |||
| for resource in folder.get_folders(): | |||
| if self._is_package(resource): | |||
| result.append(resource) | |||
| return result | |||
| import rope.base.pycore | |||
| rope.base.pycore.PyCore._find_source_folders = find_source_folders | |||
| def get_files(self): | |||
| if self.files is None: | |||
| self.files = get_python_project_files(self.project) | |||
| return self.files | |||
| rope.base.project._FileListCacher.get_files = get_files | |||
| def get_python_project_files(project): | |||
| for dirname, subdirs, files in os.walk(project.root.real_path): | |||
| for filename in files: | |||
| yield rope.base.libutils.path_to_resource( | |||
| project, os.path.join(dirname, filename), 'file') | |||
| subdirs[:] = [subdir for subdir in subdirs | |||
| if os.path.exists(os.path.join(dirname, subdir, | |||
| "__init__.py"))] | |||
| ################################################################## | |||
| # Monkey patching a method in rope because it doesn't complete import | |||
| # statements. | |||
| orig_code_completions = (rope.contrib.codeassist. | |||
| _PythonCodeAssist._code_completions) | |||
| def code_completions(self): | |||
| proposals = get_import_completions(self) | |||
| if proposals: | |||
| return proposals | |||
| else: | |||
| return orig_code_completions(self) | |||
| def get_import_completions(self): | |||
| if not self.word_finder.is_import_statement(self.offset): | |||
| return [] | |||
| modulename = self.word_finder.get_primary_at(self.offset) | |||
| # Rope can handle modules in packages | |||
| if "." in modulename: | |||
| return [] | |||
| return dict((name, FakeProposal(name)) | |||
| for name in elpy.pydocutils.get_modules() | |||
| if name.startswith(modulename)) | |||
| class FakeProposal(object): | |||
| def __init__(self, name): | |||
| self.name = name | |||
| self.type = "mock" | |||
| def get_doc(self): | |||
| return None | |||
| rope.contrib.codeassist._PythonCodeAssist._code_completions = code_completions | |||
| @ -0,0 +1,151 @@ | |||
| """A simple JSON-RPC-like server. | |||
| The server will read and write lines of JSON-encoded method calls and | |||
| responses. | |||
| See the documentation of the JSONRPCServer class for further details. | |||
| """ | |||
| import json | |||
| import sys | |||
| import traceback | |||
| class JSONRPCServer(object): | |||
| """Simple JSON-RPC-like server. | |||
| This class will read single-line JSON expressions from stdin, | |||
| decode them, and pass them to a handler. Return values from the | |||
| handler will be JSON-encoded and written to stdout. | |||
| To implement a handler, you need to subclass this class and add | |||
| methods starting with "rpc_". Methods then will be found. | |||
| Method calls should be encoded like this: | |||
| {"id": 23, "method": "method_name", "params": ["foo", "bar"]} | |||
| This will call self.rpc_method("foo", "bar"). | |||
| Responses will be encoded like this: | |||
| {"id": 23, "result": "foo"} | |||
| Errors will be encoded like this: | |||
| {"id": 23, "error": "Simple error message"} | |||
| See http://www.jsonrpc.org/ for the inspiration of the protocol. | |||
| """ | |||
| def __init__(self, stdin=None, stdout=None): | |||
| """Return a new JSON-RPC server object. | |||
| It will read lines of JSON data from stdin, and write the | |||
| responses to stdout. | |||
| """ | |||
| if stdin is None: | |||
| self.stdin = sys.stdin | |||
| else: | |||
| self.stdin = stdin | |||
| if stdout is None: | |||
| self.stdout = sys.stdout | |||
| else: | |||
| self.stdout = stdout | |||
| def read_json(self): | |||
| """Read a single line and decode it as JSON. | |||
| Can raise an EOFError() when the input source was closed. | |||
| """ | |||
| line = self.stdin.readline() | |||
| if line == '': | |||
| raise EOFError() | |||
| return json.loads(line) | |||
| def write_json(self, **kwargs): | |||
| """Write an JSON object on a single line. | |||
| The keyword arguments are interpreted as a single JSON object. | |||
| It's not possible with this method to write non-objects. | |||
| """ | |||
| self.stdout.write(json.dumps(kwargs) + "\n") | |||
| self.stdout.flush() | |||
| def handle_request(self): | |||
| """Handle a single JSON-RPC request. | |||
| Read a request, call the appropriate handler method, and | |||
| return the encoded result. Errors in the handler method are | |||
| caught and encoded as error objects. Errors in the decoding | |||
| phase are not caught, as we can not respond with an error | |||
| response to them. | |||
| """ | |||
| request = self.read_json() | |||
| if 'method' not in request: | |||
| raise ValueError("Received a bad request: {0}" | |||
| .format(request)) | |||
| method_name = request['method'] | |||
| request_id = request.get('id', None) | |||
| params = request.get('params') or [] | |||
| try: | |||
| method = getattr(self, "rpc_" + method_name, None) | |||
| if method is not None: | |||
| result = method(*params) | |||
| else: | |||
| result = self.handle(method_name, params) | |||
| if request_id is not None: | |||
| self.write_json(result=result, | |||
| id=request_id) | |||
| except Fault as fault: | |||
| error = {"message": fault.message, | |||
| "code": fault.code} | |||
| if fault.data is not None: | |||
| error["data"] = fault.data | |||
| self.write_json(error=error, id=request_id) | |||
| except Exception as e: | |||
| error = {"message": str(e), | |||
| "code": 500, | |||
| "data": {"traceback": traceback.format_exc()}} | |||
| self.write_json(error=error, id=request_id) | |||
| def handle(self, method_name, args): | |||
| """Handle the call to method_name. | |||
| You should overwrite this method in a subclass. | |||
| """ | |||
| raise Fault("Unknown method {0}".format(method_name)) | |||
| def serve_forever(self): | |||
| """Serve requests forever. | |||
| Errors are not caught, so this is a slight misnomer. | |||
| """ | |||
| while True: | |||
| try: | |||
| self.handle_request() | |||
| except (KeyboardInterrupt, EOFError, SystemExit): | |||
| break | |||
| class Fault(Exception): | |||
| """RPC Fault instances. | |||
| code defines the severity of the warning. | |||
| 2xx: Normal behavior lead to end of operation, i.e. a warning | |||
| 4xx: An expected error occurred | |||
| 5xx: An unexpected error occurred (usually includes a traceback) | |||
| """ | |||
| def __init__(self, message, code=500, data=None): | |||
| super(Fault, self).__init__(message) | |||
| self.message = message | |||
| self.code = code | |||
| self.data = data | |||
| @ -0,0 +1,275 @@ | |||
| """Method implementations for the Elpy JSON-RPC server. | |||
| This file implements the methods exported by the JSON-RPC server. It | |||
| handles backend selection and passes methods on to the selected | |||
| backend. | |||
| """ | |||
| import io | |||
| import os | |||
| import pydoc | |||
| from elpy.pydocutils import get_pydoc_completions | |||
| from elpy.rpc import JSONRPCServer, Fault | |||
| from elpy.impmagic import ImportMagic | |||
| try: | |||
| from elpy import jedibackend | |||
| except ImportError: # pragma: no cover | |||
| jedibackend = None | |||
| try: | |||
| from elpy import ropebackend | |||
| except ImportError: # pragma: no cover | |||
| ropebackend = None | |||
| class ElpyRPCServer(JSONRPCServer): | |||
| """The RPC server for elpy. | |||
| See the rpc_* methods for exported method documentation. | |||
| """ | |||
| def __init__(self): | |||
| super(ElpyRPCServer, self).__init__() | |||
| self.backend = None | |||
| self.import_magic = ImportMagic() | |||
| self.project_root = None | |||
| def _call_backend(self, method, default, *args, **kwargs): | |||
| """Call the backend method with args. | |||
| If there is currently no backend, return default.""" | |||
| meth = getattr(self.backend, method, None) | |||
| if meth is None: | |||
| return default | |||
| else: | |||
| return meth(*args, **kwargs) | |||
| def rpc_echo(self, *args): | |||
| """Return the arguments. | |||
| This is a simple test method to see if the protocol is | |||
| working. | |||
| """ | |||
| return args | |||
| def rpc_init(self, options): | |||
| self.project_root = options["project_root"] | |||
| if self.import_magic.is_enabled: | |||
| self.import_magic.build_index(self.project_root) | |||
| if ropebackend and options["backend"] == "rope": | |||
| self.backend = ropebackend.RopeBackend(self.project_root) | |||
| elif jedibackend and options["backend"] == "jedi": | |||
| self.backend = jedibackend.JediBackend(self.project_root) | |||
| elif ropebackend: | |||
| self.backend = ropebackend.RopeBackend(self.project_root) | |||
| elif jedibackend: | |||
| self.backend = jedibackend.JediBackend(self.project_root) | |||
| else: | |||
| self.backend = None | |||
| return { | |||
| 'backend': (self.backend.name if self.backend is not None | |||
| else None) | |||
| } | |||
| def rpc_get_calltip(self, filename, source, offset): | |||
| """Get the calltip for the function at the offset. | |||
| """ | |||
| return self._call_backend("rpc_get_calltip", None, filename, | |||
| get_source(source), offset) | |||
| def rpc_get_completions(self, filename, source, offset): | |||
| """Get a list of completion candidates for the symbol at offset. | |||
| """ | |||
| results = self._call_backend("rpc_get_completions", [], filename, | |||
| get_source(source), offset) | |||
| # Uniquify by name | |||
| results = list(dict((res['name'], res) for res in results) | |||
| .values()) | |||
| results.sort(key=lambda cand: _pysymbol_key(cand["name"])) | |||
| return results | |||
| def rpc_get_completion_docstring(self, completion): | |||
| """Return documentation for a previously returned completion. | |||
| """ | |||
| return self._call_backend("rpc_get_completion_docstring", | |||
| None, completion) | |||
| def rpc_get_completion_location(self, completion): | |||
| """Return the location for a previously returned completion. | |||
| This returns a list of [file name, line number]. | |||
| """ | |||
| return self._call_backend("rpc_get_completion_location", None, | |||
| completion) | |||
| def rpc_get_definition(self, filename, source, offset): | |||
| """Get the location of the definition for the symbol at the offset. | |||
| """ | |||
| return self._call_backend("rpc_get_definition", None, filename, | |||
| get_source(source), offset) | |||
| def rpc_get_docstring(self, filename, source, offset): | |||
| """Get the docstring for the symbol at the offset. | |||
| """ | |||
| return self._call_backend("rpc_get_docstring", None, filename, | |||
| get_source(source), offset) | |||
| def rpc_get_pydoc_completions(self, name=None): | |||
| """Return a list of possible strings to pass to pydoc. | |||
| If name is given, the strings are under name. If not, top | |||
| level modules are returned. | |||
| """ | |||
| return get_pydoc_completions(name) | |||
| def rpc_get_pydoc_documentation(self, symbol): | |||
| """Get the Pydoc documentation for the given symbol. | |||
| Uses pydoc and can return a string with backspace characters | |||
| for bold highlighting. | |||
| """ | |||
| try: | |||
| docstring = pydoc.render_doc(str(symbol), | |||
| "Elpy Pydoc Documentation for %s", | |||
| False) | |||
| except (ImportError, pydoc.ErrorDuringImport): | |||
| return None | |||
| else: | |||
| if isinstance(docstring, bytes): | |||
| docstring = docstring.decode("utf-8", "replace") | |||
| return docstring | |||
| def rpc_get_refactor_options(self, filename, start, end=None): | |||
| """Return a list of possible refactoring options. | |||
| This list will be filtered depending on whether it's | |||
| applicable at the point START and possibly the region between | |||
| START and END. | |||
| """ | |||
| try: | |||
| from elpy import refactor | |||
| except: | |||
| raise ImportError("Rope not installed, refactorings unavailable") | |||
| ref = refactor.Refactor(self.project_root, filename) | |||
| return ref.get_refactor_options(start, end) | |||
| def rpc_refactor(self, filename, method, args): | |||
| """Return a list of changes from the refactoring action. | |||
| A change is a dictionary describing the change. See | |||
| elpy.refactor.translate_changes for a description. | |||
| """ | |||
| try: | |||
| from elpy import refactor | |||
| except: | |||
| raise ImportError("Rope not installed, refactorings unavailable") | |||
| if args is None: | |||
| args = () | |||
| ref = refactor.Refactor(self.project_root, filename) | |||
| return ref.get_changes(method, *args) | |||
| def rpc_get_usages(self, filename, source, offset): | |||
| """Get usages for the symbol at point. | |||
| """ | |||
| source = get_source(source) | |||
| if hasattr(self.backend, "rpc_get_usages"): | |||
| return self.backend.rpc_get_usages(filename, source, offset) | |||
| else: | |||
| raise Fault("get_usages not implemented by current backend", | |||
| code=400) | |||
| def _ensure_import_magic(self): # pragma: no cover | |||
| if not self.import_magic.is_enabled: | |||
| raise Fault("fixup_imports not enabled; install importmagic module", | |||
| code=400) | |||
| if not self.import_magic.symbol_index: | |||
| raise Fault(self.import_magic.fail_message, code=200) # XXX code? | |||
| def rpc_get_import_symbols(self, filename, source, symbol): | |||
| """Return a list of modules from which the given symbol can be imported. | |||
| """ | |||
| self._ensure_import_magic() | |||
| return self.import_magic.get_import_symbols(symbol) | |||
| def rpc_add_import(self, filename, source, statement): | |||
| """Add an import statement to the module. | |||
| """ | |||
| self._ensure_import_magic() | |||
| source = get_source(source) | |||
| return self.import_magic.add_import(source, statement) | |||
| def rpc_get_unresolved_symbols(self, filename, source): | |||
| """Return a list of unreferenced symbols in the module. | |||
| """ | |||
| self._ensure_import_magic() | |||
| source = get_source(source) | |||
| return self.import_magic.get_unresolved_symbols(source) | |||
| def rpc_remove_unreferenced_imports(self, filename, source): | |||
| """Remove unused import statements. | |||
| """ | |||
| self._ensure_import_magic() | |||
| source = get_source(source) | |||
| return self.import_magic.remove_unreferenced_imports(source) | |||
| def get_source(fileobj): | |||
| """Translate fileobj into file contents. | |||
| fileobj is either a string or a dict. If it's a string, that's the | |||
| file contents. If it's a string, then the filename key contains | |||
| the name of the file whose contents we are to use. | |||
| If the dict contains a true value for the key delete_after_use, | |||
| the file should be deleted once read. | |||
| """ | |||
| if not isinstance(fileobj, dict): | |||
| return fileobj | |||
| else: | |||
| try: | |||
| with io.open(fileobj["filename"], encoding="utf-8") as f: | |||
| return f.read() | |||
| finally: | |||
| if fileobj.get('delete_after_use'): | |||
| try: | |||
| os.remove(fileobj["filename"]) | |||
| except: # pragma: no cover | |||
| pass | |||
| def _pysymbol_key(name): | |||
| """Return a sortable key index for name. | |||
| Sorting is case-insensitive, with the first underscore counting as | |||
| worse than any character, but subsequent underscores do not. This | |||
| means that dunder symbols (like __init__) are sorted after symbols | |||
| that start with an alphabetic character, but before those that | |||
| start with only a single underscore. | |||
| """ | |||
| if name.startswith("_"): | |||
| name = "~" + name[1:] | |||
| return name.lower() | |||
| @ -0,0 +1,62 @@ | |||
| (defun elpy-snippet-split-args (arg-string) | |||
| "Split a python argument string into ((name, default)..) tuples" | |||
| (mapcar (lambda (x) | |||
| (split-string x "[[:blank:]]*=[[:blank:]]*" t)) | |||
| (split-string arg-string "[[:blank:]]*,[[:blank:]]*" t))) | |||
| (defun elpy-snippet-current-method-and-args () | |||
| "Return information on the current definition." | |||
| (let ((current-defun (python-info-current-defun)) | |||
| (current-arglist | |||
| (save-excursion | |||
| (python-nav-beginning-of-defun) | |||
| (when (re-search-forward "(" nil t) | |||
| (let* ((start (point)) | |||
| (end (progn | |||
| (forward-char -1) | |||
| (forward-sexp) | |||
| (- (point) 1)))) | |||
| (elpy-snippet-split-args | |||
| (buffer-substring-no-properties start end)))))) | |||
| class method args) | |||
| (when (not current-arglist) | |||
| (setq current-arglist '(("self")))) | |||
| (if (and current-defun | |||
| (string-match "^\\(.*\\)\\.\\(.*\\)$" current-defun)) | |||
| (setq class (match-string 1 current-defun) | |||
| method (match-string 2 current-defun)) | |||
| (setq class "Class" | |||
| method "method")) | |||
| (setq args (mapcar #'car current-arglist)) | |||
| (list class method args))) | |||
| (defun elpy-snippet-init-assignments (arg-string) | |||
| "Return the typical __init__ assignments for arguments." | |||
| (let ((indentation (make-string (save-excursion | |||
| (goto-char start-point) | |||
| (current-indentation)) | |||
| ?\s))) | |||
| (mapconcat (lambda (arg) | |||
| (if (string-match "^\\*" (car arg)) | |||
| "" | |||
| (format "self.%s = %s\n%s" | |||
| (car arg) | |||
| (car arg) | |||
| indentation))) | |||
| (elpy-snippet-split-args arg-string) | |||
| ""))) | |||
| (defun elpy-snippet-super-form () | |||
| "Return (Class, first-arg).method" | |||
| (let* ((defun-info (elpy-snippet-current-method-and-args)) | |||
| (class (nth 0 defun-info)) | |||
| (method (nth 1 defun-info)) | |||
| (args (nth 2 defun-info)) | |||
| (first-arg (nth 0 args))) | |||
| (format "(%s, %s).%s" class first-arg method))) | |||
| (defun elpy-snippet-super-arguments () | |||
| "Return the argument list for the current method." | |||
| (mapconcat (lambda (x) x) | |||
| (cdr (nth 2 (elpy-snippet-current-method-and-args))) | |||
| ", ")) | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __abs__ | |||
| # key: __abs__ | |||
| # group: Special methods | |||
| # -- | |||
| def __abs__(self): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __add__ | |||
| # key: __add__ | |||
| # group: Special methods | |||
| # -- | |||
| def __add__(self, other): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __and__ | |||
| # key: __and__ | |||
| # group: Special methods | |||
| # -- | |||
| def __and__(self, other): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __bool__ | |||
| # key: __bool__ | |||
| # group: Special methods | |||
| # -- | |||
| def __bool__(self): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __call__ | |||
| # key: __call__ | |||
| # group: Special methods | |||
| # -- | |||
| def __call__(self, ${1:*args}): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __cmp__ | |||
| # key: __cmp__ | |||
| # group: Special methods | |||
| # -- | |||
| def __cmp__(self, other): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __coerce__ | |||
| # key: __coerce__ | |||
| # group: Special methods | |||
| # -- | |||
| def __coerce__(self, other): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __complex__ | |||
| # key: __complex__ | |||
| # group: Special methods | |||
| # -- | |||
| def __complex__(self): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __contains__ | |||
| # key: __contains__ | |||
| # group: Special methods | |||
| # -- | |||
| def __contains__(self, item): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __del__ | |||
| # key: __del__ | |||
| # group: Special methods | |||
| # -- | |||
| def __del__(self): | |||
| $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __delattr__ | |||
| # key: __delattr__ | |||
| # group: Special methods | |||
| # -- | |||
| def __delattr__(self, name): | |||
| $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __delete__ | |||
| # key: __delete__ | |||
| # group: Special methods | |||
| # -- | |||
| def __delete__(self, instance): | |||
| $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __delitem__ | |||
| # key: __delitem__ | |||
| # group: Special methods | |||
| # -- | |||
| def __delitem__(self, key): | |||
| $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __div__ | |||
| # key: __div__ | |||
| # group: Special methods | |||
| # -- | |||
| def __div__(self, other): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __divmod__ | |||
| # key: __divmod__ | |||
| # group: Special methods | |||
| # -- | |||
| def __divmod__(self, other): | |||
| return $0 | |||
| @ -0,0 +1,9 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __enter__ | |||
| # key: __enter__ | |||
| # group: Special methods | |||
| # -- | |||
| def __enter__(self): | |||
| $0 | |||
| return self | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __eq__ | |||
| # key: __eq__ | |||
| # group: Special methods | |||
| # -- | |||
| def __eq__(self, other): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __exit__ | |||
| # key: __exit__ | |||
| # group: Special methods | |||
| # -- | |||
| def __exit__(self, exc_type, exc_value, traceback): | |||
| $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __float__ | |||
| # key: __float__ | |||
| # group: Special methods | |||
| # -- | |||
| def __float__(self): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __floordiv__ | |||
| # key: __floordiv__ | |||
| # group: Special methods | |||
| # -- | |||
| def __floordiv__(self, other): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __ge__ | |||
| # key: __ge__ | |||
| # group: Special methods | |||
| # -- | |||
| def __ge__(self, other): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __get__ | |||
| # key: __get__ | |||
| # group: Special methods | |||
| # -- | |||
| def __get__(self, instance, owner): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __getattr__ | |||
| # key: __getattr__ | |||
| # group: Special methods | |||
| # -- | |||
| def __getattr__(self, name): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __getattribute__ | |||
| # key: __getattribute__ | |||
| # group: Special methods | |||
| # -- | |||
| def __getattribute__(self, name): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __getitem__ | |||
| # key: __getitem__ | |||
| # group: Special methods | |||
| # -- | |||
| def __getitem__(self, key): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __gt__ | |||
| # key: __gt__ | |||
| # group: Special methods | |||
| # -- | |||
| def __gt__(self, other): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __hash__ | |||
| # key: __hash__ | |||
| # group: Special methods | |||
| # -- | |||
| def __hash__(self): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __hex__ | |||
| # key: __hex__ | |||
| # group: Special methods | |||
| # -- | |||
| def __hex__(self): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __iadd__ | |||
| # key: __iadd__ | |||
| # group: Special methods | |||
| # -- | |||
| def __iadd__(self, other): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __iand__ | |||
| # key: __iand__ | |||
| # group: Special methods | |||
| # -- | |||
| def __iand__(self, other): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __idiv__ | |||
| # key: __idiv__ | |||
| # group: Special methods | |||
| # -- | |||
| def __idiv__(self, other): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __ifloordiv__ | |||
| # key: __ifloordiv__ | |||
| # group: Special methods | |||
| # -- | |||
| def __ifloordiv__(self, other): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __ilshift__ | |||
| # key: __ilshift__ | |||
| # group: Special methods | |||
| # -- | |||
| def __ilshift__(self, other): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __imod__ | |||
| # key: __imod__ | |||
| # group: Special methods | |||
| # -- | |||
| def __imod__(self, other): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __imul__ | |||
| # key: __imul__ | |||
| # group: Special methods | |||
| # -- | |||
| def __imul__(self, other): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __index__ | |||
| # key: __index__ | |||
| # group: Special methods | |||
| # -- | |||
| def __index__(self): | |||
| return $0 | |||
| @ -0,0 +1,10 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __init__ with assignment | |||
| # key: __init__ | |||
| # group: Special methods | |||
| # -- | |||
| def __init__(self${1:, args}): | |||
| """$2 | |||
| """ | |||
| ${1:$(elpy-snippet-init-assignments yas-text)} | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __instancecheck__ | |||
| # key: __instancecheck__ | |||
| # group: Special methods | |||
| # -- | |||
| def __instancecheck__(self, instance): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __int__ | |||
| # key: __int__ | |||
| # group: Special methods | |||
| # -- | |||
| def __int__(self): | |||
| $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __invert__ | |||
| # key: __invert__ | |||
| # group: Special methods | |||
| # -- | |||
| def __invert__(self): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __ior__ | |||
| # key: __ior__ | |||
| # group: Special methods | |||
| # -- | |||
| def __ior__(self, other): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __ipow__ | |||
| # key: __ipow__ | |||
| # group: Special methods | |||
| # -- | |||
| def __ipow__(self, other): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __irshift__ | |||
| # key: __irshift__ | |||
| # group: Special methods | |||
| # -- | |||
| def __irshift__(self, other): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __isub__ | |||
| # key: __isub__ | |||
| # group: Special methods | |||
| # -- | |||
| def __isub__(self, other): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __iter__ | |||
| # key: __iter__ | |||
| # group: Special methods | |||
| # -- | |||
| def __iter__(self): | |||
| $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __itruediv__ | |||
| # key: __itruediv__ | |||
| # group: Special methods | |||
| # -- | |||
| def __itruediv__(self, other): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __ixor__ | |||
| # key: __ixor__ | |||
| # group: Special methods | |||
| # -- | |||
| def __ixor__(self, other): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __le__ | |||
| # key: __le__ | |||
| # group: Special methods | |||
| # -- | |||
| def __le__(self, other): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __len__ | |||
| # key: __len__ | |||
| # group: Special methods | |||
| # -- | |||
| def __len__(self): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __long__ | |||
| # key: __long__ | |||
| # group: Special methods | |||
| # -- | |||
| def __long__(self): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __lshift__ | |||
| # key: __lshift__ | |||
| # group: Special methods | |||
| # -- | |||
| def __lshift__(self, other): | |||
| return $0 | |||
| @ -0,0 +1,7 @@ | |||
| # -*- mode: snippet -*- | |||
| # name: __lt__ | |||
| # key: __lt__ | |||
| # group: Special methods | |||
| # -- | |||
| def __lt__(self, other): | |||
| return $0 | |||