├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── autoload └── importjs.vim ├── demo.gif ├── ftplugin ├── javascript.vim ├── typescript.vim └── typescriptreact.vim └── plugin └── import-js.vim /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Here are a few tips to make it simpler to test a local copy of ImportJS in Vim. 2 | 3 | ### Symlink 4 | 5 | Make a symlink inside your pathogen bundles folder to the local copy of 6 | ImportJS to make it easier to try out your changes. 7 | 8 | ```sh 9 | ln -s ~/vim-import-js vim-import-js 10 | ``` 11 | 12 | ## Code of conduct 13 | 14 | This project adheres to the [Open Code of Conduct][code-of-conduct]. By 15 | participating, you are expected to honor this code. 16 | 17 | [code-of-conduct]: http://todogroup.org/opencodeofconduct/#Import-JS/henric.trotzig@gmail.com 18 | -------------------------------------------------------------------------------- /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 | ImportJS helps you import JavaScript dependencies. Hit a keyboard shortcut 2 | to automatically add `import x from 'y'` statements at the top of the file. 3 | 4 | ![Demo of ImportJS in action](https://raw.github.com/galooshi/vim-import-js/master/demo.gif) 5 | 6 | 7 | # Installation 8 | 9 | ImportJS is meant to be used as a Pathogen plugin. Just `git clone` this repo 10 | into the `bundles` folder and you are good to go! 11 | ``` 12 | git clone git@github.com:Galooshi/vim-import-js.git ~/.vim/bundle/vim-import-js 13 | ``` 14 | 15 | ## Dependencies 16 | 17 | ImportJS works in [Vim](http://www.vim.org/) (version 8 and later) and 18 | [Neovim](https://neovim.io/). 19 | 20 | You need import-js installed globally to use this plugin. 21 | 22 | ```sh 23 | npm install -g import-js 24 | ``` 25 | 26 | ## Default mappings 27 | 28 | By default, ImportJS attempts to set up the following mappings: 29 | 30 | Mapping | Command | Description 31 | ------------|-----------------------|--------------------------------------------------------------------- 32 | `j` | `:ImportJSWord` | Import the module for the variable under the cursor. 33 | `i` | `:ImportJSFix` | Import any missing modules and remove any modules that are not used. 34 | `g` | `:ImportJSGoto` | Go to the module of the variable under the cursor. 35 | 36 | ## Configuration 37 | For `import-js` configuration see https://github.com/Galooshi/import-js#configuration 38 | 39 | ## Troubleshooting 40 | 41 | If you run into issues when using the plugin, adding some logging can help. 42 | After starting up vim, and before you've imported anything, run this command: 43 | 44 | ``` 45 | :call ch_logfile('channel_log.txt', 'w') 46 | ``` 47 | 48 | After this, you should get useful information in `channel_log.txt`. 49 | -------------------------------------------------------------------------------- /autoload/importjs.vim: -------------------------------------------------------------------------------- 1 | function importjs#Word() 2 | let word = expand("") 3 | if (empty(word)) 4 | return 5 | endif 6 | call importjs#ExecCommand("word", word) 7 | endfunction 8 | 9 | function importjs#Goto() 10 | let word = expand("") 11 | if (empty(word)) 12 | return 13 | endif 14 | call importjs#ExecCommand("goto", word) 15 | endfunction 16 | 17 | function importjs#Fix() 18 | call importjs#ExecCommand("fix", "") 19 | endfunction 20 | 21 | " Execute the command. If we get an empty response, keep trying to execute until 22 | " we get a non-empty response or hit the max number of tries. 23 | function importjs#TryExecPayload(payload, tryCount) 24 | if (a:tryCount > 3) 25 | echoerr "No response from `importjs` after " . a:tryCount . " tries" 26 | return 27 | endif 28 | 29 | if exists("*ch_evalraw") 30 | let resultString = ch_evalraw(g:ImportJSChannel, json_encode(a:payload) . "\n") 31 | if (resultString != "") 32 | return resultString 33 | endif 34 | endif 35 | 36 | if exists("*jobsend") 37 | " Problem starting with importjs process 38 | if s:job == -1 39 | echoerr "importjs daemon process not running" 40 | return "" 41 | endif 42 | 43 | call jobsend(s:job, json_encode(a:payload) . "\n") 44 | return "" 45 | endif 46 | 47 | " We got no response, which probably means that importjs hasn't had enough 48 | " time to start up yet. Let's wait a little and try again. 49 | sleep 100m 50 | return importjs#TryExecPayload(a:payload, a:tryCount + 1) 51 | endfunction 52 | 53 | function importjs#ExecCommand(command, arg, ...) 54 | " lazy-load the background process 55 | call importjs#Init() 56 | 57 | let sendContent = (a:0 >= 1) ? a:1 : 1 58 | if sendContent == 1 59 | let fileContent = join(getline(1, '$'), "\n") 60 | else 61 | let fileContent = '' 62 | endif 63 | let payload = { 64 | \'command': a:command, 65 | \'commandArg': a:arg, 66 | \'pathToFile': expand("%:p"), 67 | \'fileContent': fileContent, 68 | \} 69 | 70 | try 71 | let resultString = importjs#TryExecPayload(payload, 0) 72 | catch /E906:/ 73 | " channel not open 74 | echoerr "importjs process not running" 75 | return 76 | endtry 77 | 78 | if (resultString != "") 79 | return importjs#ParseResult(resultString) 80 | endif 81 | endfunction 82 | 83 | function importjs#ParseResult(resultString) 84 | let result = json_decode(a:resultString) 85 | 86 | if (has_key(result, 'error')) 87 | echoerr result.error 88 | return 89 | endif 90 | 91 | if (has_key(result, 'goto')) 92 | execute "edit " . result.goto 93 | return 94 | endif 95 | 96 | if (has_key(result, 'modules')) 97 | " Simply return the list of modules returned in a search. This can be used 98 | " by other plugins wanting to make use of the import-js search function. 99 | return result.modules 100 | endif 101 | 102 | let fileContent = join(getline(1, '$'), "\n") 103 | if (result.fileContent != fileContent) 104 | call importjs#ReplaceBuffer(result.fileContent) 105 | endif 106 | 107 | if (has_key(result, 'messages') && len(result.messages)) 108 | call importjs#Msg(join(result.messages, "\n")) 109 | endif 110 | if (has_key(result, 'unresolvedImports') && len(result.unresolvedImports)) 111 | call importjs#Resolve(result.unresolvedImports) 112 | endif 113 | endfunction 114 | 115 | function importjs#Resolve(unresolvedImports) 116 | let resolved = {} 117 | 118 | " Save cursor position so that we can restore it later 119 | let cursorPos = getpos(".") 120 | 121 | for [word, alternatives] in items(a:unresolvedImports) 122 | let wordWithBoundaries = "\\<" . word . "\\>" 123 | " Highlight the word in the buffer 124 | let match = matchadd("Search", wordWithBoundaries) 125 | try 126 | " Jump to the word 127 | execute ":ijump " . wordWithBoundaries 128 | catch /E387:/ 129 | " we're already on that line 130 | endtry 131 | 132 | let options = ["ImportJS: Select module to import for `" . word . "`:"] 133 | let index = 0 134 | for alternative in alternatives 135 | let index = index + 1 136 | call add(options, index . ": " . alternative.displayName) 137 | endfor 138 | call inputsave() 139 | 140 | " Clear out previous message. This is particularly important if there are 141 | " multiple unresolved imports that we will be prompting for. 142 | call importjs#Msg("") 143 | 144 | let selection = inputlist(options) 145 | 146 | call inputrestore() 147 | 148 | " Remove the highlight 149 | call matchdelete(match) 150 | 151 | if (selection > 0 && selection < len(options)) 152 | let resolved[word] = alternatives[selection - 1].data 153 | endif 154 | endfor 155 | 156 | call setpos(".", cursorPos) 157 | 158 | if (len(resolved)) 159 | call importjs#ExecCommand("add", resolved) 160 | endif 161 | endfunction 162 | 163 | 164 | function importjs#ReplaceBuffer(content) 165 | " Save cursor position so that we can restore it later 166 | let cursorPos = getpos(".") 167 | let originalLineCount = line("$") 168 | " Delete all lines from the buffer 169 | execute "%d" 170 | " Write the resulting content into the buffer 171 | let @a = a:content 172 | normal! G 173 | execute "put a" 174 | " Remove lingering line at the top: 175 | execut ":1d" 176 | " Restore cursor position, attempting to compensate for the resulting 177 | " imports moving the original line up or down 178 | let newLineCount = line("$") 179 | let cursorPos[1] = cursorPos[1] + newLineCount - originalLineCount 180 | call setpos(".", cursorPos) 181 | endfunction 182 | 183 | " Prints [long] message up to (&columns-1) length 184 | " guaranteed without "Press Enter" prompt. 185 | " http://vim.wikia.com/wiki/How_to_print_full_screen_width_messages 186 | function! importjs#Msg(msg) 187 | let x=&ruler | let y=&showcmd 188 | set noruler noshowcmd 189 | redraw 190 | echo a:msg 191 | let &ruler=x | let &showcmd=y 192 | endfun 193 | 194 | function! importjs#JobExit(job, exitstatus) 195 | if (a:exitstatus == 127) 196 | echoerr "importjs command not found. Run `npm install import-js` to get it." 197 | echoerr "" 198 | endif 199 | endfun 200 | 201 | " Holds job output that must be joined across multiple outputs for Neovim. 202 | let s:neovim_job_output = '' 203 | 204 | " Neovim sends output in 8192 byte chunks, we must join all of the chunks 205 | " before handling them. Inspired by https://git.io/v7HcP 206 | function! importjs#JoinNeovimOutput(last_line, data) abort 207 | let l:lines = a:data[:-2] 208 | 209 | if len(a:data) > 1 210 | let l:lines[0] = a:last_line . l:lines[0] 211 | let l:new_last_line = a:data[-1] 212 | else 213 | let l:new_last_line = a:last_line . a:data[0] 214 | endif 215 | 216 | for l:line in l:lines 217 | call importjs#HandleJoinedNeovimInput(l:line) 218 | endfor 219 | 220 | return l:new_last_line 221 | endfunction 222 | 223 | function! importjs#HandleJoinedNeovimInput(line) 224 | if strpart(a:line, 0, 1) == "{" 225 | call importjs#ParseResult(a:line) 226 | endif 227 | endfunction 228 | 229 | " Neovim job handler 230 | function! s:JobHandler(job_id, data, event) dict 231 | if a:event == 'stdout' 232 | let s:neovim_job_output = importjs#JoinNeovimOutput( 233 | \ s:neovim_job_output, 234 | \ a:data 235 | \) 236 | elseif a:event == 'stderr' 237 | echoerr "import-js error: " . join(a:data) 238 | endif 239 | endfunction 240 | 241 | function! importjs#Init() 242 | if exists("s:job") 243 | return 244 | endif 245 | 246 | let s:callbacks = { 247 | \ 'on_stdout': function('s:JobHandler'), 248 | \ 'on_stderr': function('s:JobHandler'), 249 | \ 'on_exit': function('s:JobHandler') 250 | \ } 251 | 252 | " Include the PID of the parent (this Vim process) to make `ps` output more 253 | " useful. 254 | 255 | if has('win32') || has('win64') 256 | let s:job_executable='importjs.cmd' 257 | else 258 | let s:job_executable='importjs' 259 | endif 260 | 261 | if exists("*jobstart") 262 | " neovim 263 | let s:job = jobstart([s:job_executable, 'start', '--parent-pid', getpid()], s:callbacks) 264 | elseif exists("*job_start") 265 | " vim 266 | let s:job=job_start([s:job_executable, 'start', '--parent-pid', getpid()], { 267 | \'exit_cb': 'importjs#JobExit', 268 | \}) 269 | 270 | let g:ImportJSChannel=job_getchannel(s:job) 271 | " ignore first line of output, which is something like 272 | " > ImportJS (v2.10.1) DAEMON active. 273 | call ch_readraw(g:ImportJSChannel, { "timeout": 2000 }) 274 | endif 275 | endfunction 276 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Galooshi/vim-import-js/0f175bbfc4d551a541821414b4d5da1235010a91/demo.gif -------------------------------------------------------------------------------- /ftplugin/javascript.vim: -------------------------------------------------------------------------------- 1 | if !hasmapto(':ImportJSWord') && maparg('j', 'n') == '' 2 | silent! nnoremap j :ImportJSWord 3 | endif 4 | 5 | if !hasmapto(':ImportJSFix') && maparg('i', 'n') == '' 6 | silent! nnoremap i :ImportJSFix 7 | endif 8 | 9 | if !hasmapto(':ImportJSGoto') && maparg('g', 'n') == '' 10 | silent! nnoremap g :ImportJSGoto 11 | endif 12 | -------------------------------------------------------------------------------- /ftplugin/typescript.vim: -------------------------------------------------------------------------------- 1 | if !hasmapto(':ImportJSWord') && maparg('j', 'n') == '' 2 | silent! nnoremap j :ImportJSWord 3 | endif 4 | 5 | if !hasmapto(':ImportJSFix') && maparg('i', 'n') == '' 6 | silent! nnoremap i :ImportJSFix 7 | endif 8 | 9 | if !hasmapto(':ImportJSGoto') && maparg('g', 'n') == '' 10 | silent! nnoremap g :ImportJSGoto 11 | endif 12 | -------------------------------------------------------------------------------- /ftplugin/typescriptreact.vim: -------------------------------------------------------------------------------- 1 | if !hasmapto(':ImportJSWord') && maparg('j', 'n') == '' 2 | silent! nnoremap j :ImportJSWord 3 | endif 4 | 5 | if !hasmapto(':ImportJSFix') && maparg('i', 'n') == '' 6 | silent! nnoremap i :ImportJSFix 7 | endif 8 | 9 | if !hasmapto(':ImportJSGoto') && maparg('g', 'n') == '' 10 | silent! nnoremap g :ImportJSGoto 11 | endif 12 | -------------------------------------------------------------------------------- /plugin/import-js.vim: -------------------------------------------------------------------------------- 1 | command! ImportJSWord call importjs#Word() 2 | command! ImportJSGoto call importjs#Goto() 3 | command! ImportJSFix call importjs#Fix() 4 | --------------------------------------------------------------------------------