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