├── LICENSE ├── README.md └── plugin └── import-js.el /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Henric Trotzig 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![MELPA](http://melpa.org/packages/import-js-badge.svg)](http://melpa.org/#/import-js) 2 | 3 | # Running ImportJS in Emacs 4 | 5 | 1. Install the `importjs` binary: 6 | * `npm install import-js -g` 7 | 2. Configure ImportJS 8 | * See [Configuration](README.md#configuration) 9 | 3. Install import-js.el for Emacs 10 | * Install via [MELPA](https://melpa.org/#/import-js) 11 | * Alternatively, Copy plugins/import-js.el into your Emacs load-path and add 12 | `(require 'import-js)` to your config. You will also need to install 13 | [grizzl](https://github.com/grizzl/grizzl) 14 | 4. Run the import-js daemon 15 | * `(M-x) run-import-js` 16 | * The daemon will use watchman if installed to improve performance 17 | 5. Import a file! 18 | * You can use something like `(M-x) import-js-import` with your cursor over 19 | the desired module 20 | * It will be helpful to bind `import-js-import` to an easy-to-use binding, 21 | such as: 22 | 23 | ``` 24 | (define-prefix-command 'my-keymap) 25 | (global-set-key (kbd "s-a") 'my-keymap) 26 | (define-key my-keymap (kbd "a u") 'import-js-import) 27 | ``` 28 | 6. Go directly to a file 29 | * The ImportJS goto interface allows us to jump to a package 30 | * `(M-x) import-js-goto` will jump to the appropriate file found by ImportJS 31 | * This should also be bound to something useful: 32 | `(global-set-key (kbd "") 'import-js-goto)` 33 | 7. Fix your imports 34 | * Optionally, you can configure ImportJS to fix your imports for you, adding 35 | unknown variables and removing unused imports. ImportJS uses eslint to find 36 | these variables. 37 | * `eslint` must be in your PATH. 38 | * eslint plugins must be installed for that specific version of eslint (if 39 | eslint is a global eslint, you may need to install the plugins globally) 40 | * Run with `(M-x) import-js-fix` 41 | * You can also configure `import-js-fix` to run on save: 42 | `(add-hook 'after-save-hook 'import-js-fix)` 43 | 44 | # Note on Node v6.1.0 45 | 46 | I've had issues running import-js in Emacs on Node v6.1.0. Node >= v6.2.0 seems 47 | to work as expected. 48 | -------------------------------------------------------------------------------- /plugin/import-js.el: -------------------------------------------------------------------------------- 1 | ;;; import-js.el --- Import Javascript dependencies -*- lexical-binding: t; -*- 2 | ;; Copyright (C) 2015 Henric Trotzig and Kevin Kehl 3 | ;; 4 | ;; Author: Kevin Kehl 5 | ;; URL: http://github.com/Galooshi/emacs-import-js/ 6 | ;; Package-Requires: ((grizzl "0.1.0") (emacs "24")) 7 | ;; Version: 1.0.0 8 | ;; Keywords: javascript 9 | 10 | ;; This file is not part of GNU Emacs. 11 | 12 | ;;; License: 13 | 14 | ;; Licensed under the MIT license, see: 15 | ;; http://github.com/Galooshi/emacs-import-js/blob/master/LICENSE 16 | 17 | ;;; Commentary: 18 | 19 | ;; Quick start: 20 | ;; run-import-js 21 | ;; 22 | ;; Bind the following commands: 23 | ;; import-js-import 24 | ;; import-js-goto 25 | ;; 26 | ;; For a detailed introduction see: 27 | ;; http://github.com/Galooshi/emacs-import-js/blob/master/README.md 28 | 29 | ;;; Code: 30 | 31 | (require 'json) 32 | 33 | (eval-when-compile 34 | (require 'grizzl)) 35 | 36 | (defvar import-js-handler nil "Current import-js output handler") 37 | (defvar import-js-handler-buffer nil "Current import-js buffer to write to when handler is invoked.") 38 | (defvar import-js-output "" "Partial import-js output") 39 | (defvar import-js-process nil "Current import-js process") 40 | (defvar import-js-current-project-root nil "Current project root") 41 | 42 | (defun import-js-check-daemon () 43 | (unless import-js-process 44 | (throw 'import-js-daemon "import-js-daemon not running"))) 45 | 46 | (defun import-js-json-encode-alist (alist) 47 | (let ((json-encoding-pretty-print nil)) 48 | (json-encode-alist alist))) 49 | 50 | (defun import-js-send-input (input-alist) 51 | "Append the current buffer content and path to file to a data alist, and send to import-js" 52 | (let ((input-json (import-js-json-encode-alist (append input-alist 53 | `(("fileContent" . ,(buffer-string)) 54 | ("pathToFile" . ,(buffer-file-name))))))) 55 | (setq import-js-handler-buffer (current-buffer)) 56 | (process-send-string import-js-process input-json) 57 | (process-send-string import-js-process "\n"))) 58 | 59 | (defun import-js-locate-project-root (path) 60 | "Find the dir containing package.json by walking up the dir tree from path" 61 | (let ((parent-dir (file-name-directory path)) 62 | (project-found nil)) 63 | (while (and parent-dir (not (setf project-found (file-exists-p (concat parent-dir "package.json"))))) 64 | (let* ((pd (file-name-directory (directory-file-name parent-dir))) 65 | (pd-exists (not (equal pd parent-dir)))) 66 | (setf parent-dir (if pd-exists pd nil)))) 67 | (if project-found parent-dir "."))) 68 | 69 | (defun import-js-word-at-point () 70 | "Get the module of interest" 71 | (save-excursion 72 | (skip-chars-backward "A-Za-z0-9:_") 73 | (let ((beg (point)) module) 74 | (skip-chars-forward "A-Za-z0-9:_") 75 | (setq module (buffer-substring-no-properties beg (point))) 76 | module))) 77 | 78 | (defun import-js-write-content (import-data) 79 | "Write output data from import-js to the buffer" 80 | (let ((file-content (cdr (assoc 'fileContent import-data)))) 81 | (write-region file-content nil (buffer-file-name import-js-handler-buffer))) 82 | (save-window-excursion 83 | (switch-to-buffer import-js-handler-buffer) 84 | (revert-buffer t t t)) 85 | (setq import-js-handler-buffer nil)) 86 | 87 | (defun import-js-add (add-alist) 88 | "Resolves an import with multiple matches" 89 | (import-js-send-input `(("command" . "add") 90 | ("commandArg" . ,add-alist)))) 91 | 92 | (defun import-js-handle-unresolved (unresolved word) 93 | "Map unresolved imports to a path" 94 | (let ((paths (mapcar 95 | (lambda (car) 96 | (cdr (assoc 'importPath car))) 97 | (cdr (assoc-string word unresolved))))) 98 | (minibuffer-with-setup-hook 99 | (lambda () (make-sparse-keymap)) 100 | (grizzl-completing-read (format "Unresolved import (%s)" word) 101 | (grizzl-make-index 102 | paths 103 | 'files 104 | import-js-current-project-root 105 | nil))))) 106 | 107 | (defun import-js-handle-data (process data) 108 | "Handles STDOUT from node, which arrives in chunks" 109 | (setq import-js-output (concat import-js-output data)) 110 | (if (string-match "DAEMON active" data) 111 | ;; Ignore the startup message 112 | (setq import-js-output "") 113 | (when (string-match "\n$" data) 114 | (let ((import-data import-js-output)) 115 | (setq import-js-output "") 116 | (funcall import-js-handler import-data))))) 117 | 118 | (defun import-js-handle-imports (import-data) 119 | "Check to see if import is unresolved. If resolved, write file. Else, prompt the user to select" 120 | (let* ((import-alist (json-read-from-string import-data)) 121 | (unresolved (cdr (assoc 'unresolvedImports import-alist)))) 122 | (if unresolved 123 | (import-js-add (mapcar 124 | (lambda (word) 125 | (let ((key (car word))) 126 | (cons key (import-js-handle-unresolved unresolved key)))) 127 | unresolved)) 128 | (import-js-write-content import-alist)))) 129 | 130 | (defun import-js-handle-goto (import-data) 131 | "Navigate to the indicated file" 132 | (let ((goto-list (json-read-from-string import-data))) 133 | (find-file (cdr (assoc 'goto goto-list))))) 134 | 135 | ;;;###autoload 136 | (defun import-js-import () 137 | "Run import-js on a particular module" 138 | (interactive) 139 | (import-js-check-daemon) 140 | (setq import-js-output "") 141 | (setq import-js-handler 'import-js-handle-imports) 142 | (import-js-send-input `(("command" . "word") 143 | ("commandArg" . ,(import-js-word-at-point))))) 144 | 145 | ;;;###autoload 146 | (defun import-js-fix () 147 | "Run import-js on an entire file, importing or fixing as necessary" 148 | (interactive) 149 | (import-js-check-daemon) 150 | (setq import-js-output "") 151 | (setq import-js-handler 'import-js-handle-imports) 152 | (import-js-send-input `(("command" . "fix")))) 153 | 154 | ;;;###autoload 155 | (defun import-js-goto () 156 | "Run import-js goto function, which returns a path to the specified module" 157 | (interactive) 158 | (import-js-check-daemon) 159 | (setq import-js-output "") 160 | (setq import-js-handler 'import-js-handle-goto) 161 | (import-js-send-input `(("command" . "goto") 162 | ("commandArg" . ,(import-js-word-at-point))))) 163 | 164 | ;;;###autoload 165 | (defun run-import-js () 166 | "Start the import-js daemon" 167 | (interactive) 168 | (kill-import-js) 169 | (let ((process-connection-type nil)) 170 | (setq import-js-process (start-process "import-js" nil "importjs" "start" (format "--parent-pid=%s" (emacs-pid)))) 171 | (set-process-filter import-js-process 'import-js-handle-data))) 172 | 173 | ;;;###autoload 174 | (defun kill-import-js () 175 | "Kill the currently running import-js daemon process" 176 | (interactive) 177 | (if import-js-process 178 | (delete-process import-js-process))) 179 | 180 | (provide 'import-js) 181 | ;;; import-js.el ends here 182 | --------------------------------------------------------------------------------