├── .gitignore ├── README.md ├── autoload └── purescript │ ├── ide.vim │ ├── ide │ ├── import.vim │ └── utils.vim │ └── job.vim ├── doc └── psc-ide-vim.txt ├── ftplugin └── purescript_pscide.vim └── syntax_checkers └── purescript └── pscide.vim /.gitignore: -------------------------------------------------------------------------------- 1 | /doc/tags 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # psc-ide-vim 2 | A vim plugin that interfaces with `purs ide`, PureScript's editor support 3 | server. 4 | 5 | ## Setup 6 | Installing the plugin via Vundle/NeoBundle/etc: 7 | 8 | `NeoBundle "frigoeu/psc-ide-vim"` 9 | 10 | If you manually install the plugin don't forget to generate help tags with 11 | `:helptags` vim commands: 12 | ``` 13 | helptags ~/.vim/bundles/psc-ide-vim/doc 14 | ``` 15 | Just change the path to one that points to the `doc` dir. 16 | 17 | ## Syntax checking 18 | This plugin provides two kinds of syntax checking with syntastic. Controlling 19 | which one you use happens via the global variable `g:psc_ide_syntastic_mode`. 20 | 21 | - if `0` -> syntax checking is disabled, but `:Prebuild` will run whenever the 22 | file is saved (an your quickfix will be synced) 23 | - if `1` (default) -> syntax checking happens with the fast-rebuild feature of 24 | psc-ide. This only checks the file that you're currently saving. You need psc 25 | version >= 0.8.5.0 for this to work. 26 | - if `2` -> use pulp build. This rebuilds the whole project and is often quite 27 | a bit slower, so using the fast-rebuild mode is advised. 28 | 29 | ![:PSCIDE syntastic gif](http://frigoeu.github.io/gifs/syntastic.gif) 30 | 31 | ## Commands 32 | 33 | Check `:help psc-ide-vim` or 34 | [here](https://github.com/FrigoEU/psc-ide-vim/blob/master/doc/psc-ide-vim.txt). 35 | 36 | ## Mappings 37 | No custom mappings are provided, but it's easy to map the above commands to any 38 | key mapping you want. You can add these mappings to 39 | `after/ftplugin/purescript.vim`: 40 | 41 | ``` 42 | nm L :Plist 43 | nm l :Pload! 44 | nm r :Prebuild! 45 | nm f :PaddClause 46 | nm t :PaddType 47 | nm a :Papply 48 | nm A :Papply! 49 | nm C :Pcase! 50 | nm i :Pimport 51 | nm qa :PaddImportQualifications 52 | nm g :Pgoto 53 | nm p :Pursuit 54 | nm T :Ptype 55 | ``` 56 | 57 | ## Omnicompletion and user completion 58 | Omnicompletion gets possibilities based on the word under your cursor, and 59 | shows the types. 60 | 61 | ![:PSCIDE omnicompletion gif](http://frigoeu.github.io/gifs/omnicompletion.gif) 62 | 63 | ## Prerequisites 64 | * Vim version 8 or higher or NeoVim version 0.2.0 or higher. 65 | * purs installed and available on your path 66 | * [purescript-vim](https://github.com/raichoo/purescript-vim) 67 | * `bower.json`, `psc-package.json` or `spago.dhall` file in the root path of your project 68 | 69 | ## Debugging 70 | Add the following to have psc-ide-vim spit out all logs: 71 | ``` 72 | let g:psc_ide_log_level = 3 73 | ``` 74 | -------------------------------------------------------------------------------- /autoload/purescript/ide.vim: -------------------------------------------------------------------------------- 1 | let s:started = v:false 2 | let s:external = v:false 3 | let s:valid = v:false 4 | 5 | fun! purescript#ide#started() 6 | return s:started 7 | endfun 8 | 9 | fun! purescript#ide#setStarted(val) 10 | let s:started = a:val 11 | endfun 12 | 13 | fun! purescript#ide#external() 14 | return s:external 15 | endfun 16 | 17 | fun! purescript#ide#setExternal(val) 18 | let s:external = a:val 19 | endfun 20 | 21 | fun! purescript#ide#valid() 22 | return s:valid 23 | endfun 24 | 25 | fun! purescript#ide#setValid(val) 26 | let s:valid = a:val 27 | endfun 28 | 29 | fun! purescript#ide#call(input, errorm, isRetry, cb, ...) 30 | let silent = a:0 >= 1 ? a:1 : v:false 31 | 32 | call purescript#ide#utils#debug("purescript#ide#call: command: " . json_encode(a:input), 3) 33 | 34 | if !s:valid 35 | call PSCIDEprojectValidate(v:true) 36 | endif 37 | 38 | if !s:started 39 | call purescript#ide#utils#debug("purescript#ide#call: no server found", 1) 40 | 41 | let expectedCWD = fnamemodify(purescript#ide#utils#findRoot(), ":p:h") 42 | let cwdcommand = {'command': 'cwd'} 43 | 44 | let jobid = purescript#job#start( 45 | \ ["purs", "ide", "client", "-p", g:psc_ide_server_port], 46 | \ { "on_stdout": {ch, msg -> s:startFn(a:input, a:errorm, a:cb, cwdcommand, msg, silent)} 47 | \ , "on_stderr": {ch, err -> purescript#ide#utils#debug("purescript#ide#call error: " . string(err), 3)} 48 | \ }) 49 | call purescript#job#send(jobid, json_encode(cwdcommand) . "\n") 50 | return 51 | endif 52 | 53 | let enc = json_encode(a:input) 54 | let jobid = purescript#job#start( 55 | \ ["purs", "ide", "client", "-p", g:psc_ide_server_port], 56 | \ { "on_stdout": {ch, msg -> a:cb(s:callFn(a:input, a:errorm, a:isRetry, a:cb, msg))} 57 | \ , "on_stderr": {ch, err -> purescript#ide#utils#debug("purescript#ide#call error: " . purescript#ide#utils#toString(err), 0)} 58 | \ }) 59 | call purescript#job#send(jobid, enc . "\n") 60 | " call purescript#job#stop(jobid) " Not needed I think, \n stops job 61 | endfun 62 | 63 | fun! purescript#ide#callSync(input, errorm, isRetry) 64 | call purescript#ide#utils#debug("purescript#ide#callSync: command: " . json_encode(a:input), 3) 65 | 66 | if !s:valid 67 | call PSCIDEprojectValidate(v:true) 68 | endif 69 | 70 | if !s:started 71 | let expectedCWD = fnamemodify(purescript#ide#utils#findRoot(), ":p:h") 72 | let cwdcommand = {'command': 'cwd'} 73 | 74 | call purescript#ide#utils#debug("purescript#ide#callSync: no server found", 1) 75 | let cwdresp = system("purs 2>/dev/null ide client -p " . g:psc_ide_server_port, json_encode(cwdcommand)) 76 | return s:startFn(a:input, a:errorm, 0, cwdcommand, cwdresp) 77 | else 78 | call purescript#ide#utils#debug("purescript#ide#callSync: trying to reach server again", 1) 79 | let enc = json_encode(a:input) 80 | let resp = system("purs 2>/dev/null ide client -p " . g:psc_ide_server_port, enc) 81 | return s:callFn(a:input, a:errorm, a:isRetry, 0, resp) 82 | endif 83 | endfun 84 | 85 | fun! s:startFn(input, errorm, cb, cwdcommand, cwdresp, ...) 86 | let silent = a:0 >= 1 ? a:1 : v:false 87 | 88 | let cwd = fnamemodify(purescript#ide#utils#findRoot(), ":p:h") 89 | try 90 | let cwdrespDecoded = json_decode(a:cwdresp) 91 | catch /.*/ 92 | let cwdrespDecoded = {"resultType": "failed", "error": a:cwdresp} 93 | endtry 94 | 95 | call purescript#ide#utils#debug("s:startFn: resp: " . json_encode(cwdrespDecoded), 1) 96 | 97 | if type(cwdrespDecoded) == v:t_dict && cwdrespDecoded.resultType ==# 'success' 98 | if cwd != cwdrespDecoded.result 99 | call purescript#ide#utils#debug("s:startFn: found server, re-starting", 1) 100 | call PSCIDEend() 101 | call PSCIDEstart(1) 102 | else 103 | if !silent 104 | call purescript#ide#utils#log("started", v:true) 105 | endif 106 | let s:started = v:true 107 | let s:external = v:true 108 | endif 109 | else 110 | call purescript#ide#utils#debug("s:startFn: starting new server", 1) 111 | call PSCIDEstart(1) 112 | endif 113 | call purescript#ide#utils#debug("s:startFn: resending", 1) 114 | if (type(a:cb) == type(0) && !a:cb) 115 | let cwdresp = system( 116 | \ "purs 2>/dev/null ide client -p" . g:psc_ide_server_port, 117 | \ json_encode(a:cwdcommand) 118 | \ ) 119 | call s:retryFn(a:input, a:errorm, 0, cwd, cwdresp) 120 | else 121 | let jobid = purescript#job#start( 122 | \ ["purs", "ide", "client", "-p", g:psc_ide_server_port], 123 | \ { "on_stdout": { ch, resp -> s:retryFn(a:input, a:errorm, a:cb, cwd, resp, silent) } 124 | \ , "on_stderr": { ch, err -> silent ? purescript#ide#utils#warn(purescript#ide#utils#toString(err)) : v:null } 125 | \ } 126 | \) 127 | call purescript#job#send(jobid, json_encode(a:cwdcommand) . "\n") 128 | endif 129 | endfun 130 | 131 | fun! s:retryFn(input, errorm, cb, expectedCWD, cwdresp2, ...) 132 | let silent = a:0 >= 1 ? a:1 : v:false 133 | call purescript#ide#utils#debug("s:retryFn: response: " . json_encode(a:cwdresp2), 1) 134 | 135 | if type(a:cwdresp2) == v:t_list 136 | let json = a:cwdresp2[0] 137 | else 138 | let json = a:cwdresp2 139 | endif 140 | 141 | try 142 | let cwdresp2Decoded = json_decode(json) 143 | catch /.*/ 144 | let cwdresp2Decoded = {"resultType": "failed", "error": a:cwdresp2} 145 | endtry 146 | 147 | if type(cwdresp2Decoded) == v:t_dict && cwdresp2Decoded.resultType ==# 'success' 148 | \ && cwdresp2Decoded.result == a:expectedCWD 149 | call purescript#ide#utils#debug("s:retryFn: success", 1) 150 | call PSCIDEload(1, "") 151 | else 152 | if type(cwdresp2Decoded) == v:t_dict 153 | let error = get(cwdresp2Decoded, "error", []) 154 | if type(error) == v:t_list && len(error) && !silent 155 | call purescript#ide#utils#warn(join(error, " "), v:true) 156 | endif 157 | endif 158 | return 159 | endif 160 | 161 | let enc = json_encode(a:input) 162 | if (type(a:cb) == type(0)) 163 | let resp = system( 164 | \ "purs 2>/dev/null ide client -p" . g:psc_ide_server_port, 165 | \ enc 166 | \ ) 167 | return s:callFn(a:input, a:errorm, 1, 0, resp) 168 | endif 169 | 170 | if (type(a:cb) == type(0) && !a:cb) 171 | let resp = system( 172 | \ "purs 2>/dev/null ide client -p" . g:psc_ide_server_port 173 | \ enc 174 | \ ) 175 | return s:callFn(a:input, a:errorm, 1, 0, resp) 176 | endif 177 | call purescript#ide#utils#debug("callPscIde: command: " . enc, 3) 178 | let jobid = purescript#job#start( 179 | \ ["purs", "ide", "client", "-p", g:psc_ide_server_port], 180 | \ { "on_stdout": {ch, resp -> a:cb(s:callFn(a:input, a:errorm, 1, a:cb, resp, silent))} 181 | \ , "on_stderr": {ch, err -> purescript#ide#utils#debug("s:retryFn error: " . err, 3)} 182 | \ }) 183 | call purescript#job#send(jobid, enc . "\n") 184 | endfun 185 | 186 | fun! s:callFn(input, errorm, isRetry, cb, resp, ...) 187 | let silent = a:0 >= 1 ? a:1 : v:false 188 | call purescript#ide#utils#debug("s:callFn: response: " . json_encode(a:resp), 3) 189 | 190 | if (type(a:resp) == type([])) 191 | let json = a:resp[0] 192 | else 193 | let json = a:resp 194 | endif 195 | 196 | try 197 | let decoded = json_decode(json) 198 | catch /.*/ 199 | let s:started = v:false 200 | let s:external = v:false 201 | let decoded = 202 | \ { "resultType": "error" 203 | \ , "result": "failed to decode response" 204 | \ } 205 | 206 | if a:isRetry 207 | if !silent 208 | call purescript#ide#utils#log("failed to contact server", v:true) 209 | endif 210 | else 211 | " Seems saving often causes `purs ide server` to crash. Haven't been able 212 | " to figure out why. It doesn't crash when I run it externally... 213 | " retrying is then the next best thing 214 | return purescript#ide#call(a:input, a:errorm, 1, a:cb, silent) " Keeping track of retries so we only retry once 215 | endif 216 | endtry 217 | 218 | if (type(decoded) != type({}) || decoded['resultType'] !=# 'success') 219 | \ && type(a:errorm) == v:t_string 220 | call purescript#ide#utils#log(a:errorm) 221 | endif 222 | return decoded 223 | endfun 224 | 225 | fun! purescript#ide#handlePursError(resp) 226 | if type(a:resp) == v:t_dict 227 | call purescript#ide#utils#error(get(a:resp, "result", "error")) 228 | elseif type(a:resp) == v:t_string 229 | call purescript#ide#utils#error(a:resp) 230 | endif 231 | endfun 232 | -------------------------------------------------------------------------------- /autoload/purescript/ide/import.vim: -------------------------------------------------------------------------------- 1 | fun! s:FilterPrelude(respResults) 2 | call filter(a:respResults, { idx, r -> index(g:psc_ide_prelude, r.module) == -1 }) 3 | endfun 4 | 5 | fun! s:FilterTopFn(module, modules) 6 | " module :: Array String 7 | " modules :: Array (Array String) 8 | let mods = map(copy(g:psc_ide_filter_submodules_do_not_hide), { idx, m -> split(m, '\.') }) 9 | return empty(filter(copy(a:modules), { idx, m -> s:IsSubmodule(a:module, m, mods) })) 10 | endfun 11 | 12 | fun! s:IsSubmodule(m1, m2, mods) 13 | " is m1 a submodule of m2 14 | " m1 :: Array String 15 | " m2 :: Array String 16 | if index(a:mods, a:m1) != -1 17 | let res = v:false 18 | else 19 | if len(a:m1) > len(a:m2) 20 | let res = a:m1[0:len(a:m2)-1] == a:m2 ? v:true : v:false 21 | else 22 | let res = v:false 23 | endif 24 | endif 25 | return res 26 | endfun 27 | 28 | fun! s:FilterTop(respResults) 29 | let modules = map(copy(a:respResults), { idx, r -> split(r.module, '\.') }) 30 | call filter(a:respResults, { idx, r -> s:FilterTopFn(split(r.module, '\.'), modules) }) 31 | endfun 32 | 33 | function! purescript#ide#import#listImports(module, ...) 34 | if a:0 >= 1 35 | let qualifier = a:1 36 | else 37 | let qualifier = "" 38 | endif 39 | 40 | if a:0 >= 2 41 | let ident = a:2 42 | else 43 | let ident = "" 44 | endif 45 | 46 | call purescript#ide#utils#update() 47 | let filename = expand("%:p") 48 | let resp = purescript#ide#callSync( 49 | \ {'command': 'list', 'params': {'type': 'import', 'file': filename}}, 50 | \ 'Failed to get imports for: ' . a:module, 51 | \ 0 52 | \ ) 53 | call purescript#ide#utils#debug("PSCIDE purescript#ide#import#listImports result: " . string(resp), 3) 54 | 55 | " Only need module names right now, so pluck just those. 56 | if type(resp) == v:t_dict && resp['resultType'] ==# 'success' 57 | 58 | " psc-ide >=0.11 returns imports on 'imports' property. 59 | if type(resp.result) == v:t_list 60 | let results = resp.result 61 | else 62 | let results = resp.result.imports 63 | endif 64 | if !empty(qualifier) 65 | call filter(results, { idx, val -> get(val, "qualifier", "") == qualifier }) 66 | end 67 | if !empty(ident) 68 | call filter(results, {idx, val -> get(val, "importType", "") == "explicit" && has_key(val, "identifiers") ? index(val.identifiers, ident) != -1 : v:true}) 69 | endif 70 | return results 71 | else 72 | call purescript#ide#handlePursError(resp) 73 | return [] 74 | endif 75 | endfunction 76 | 77 | 78 | " Return line number of last import line (including blank lines). 79 | " 80 | " It will fail to find the proper line on 81 | " ``` 82 | " import Prelude 83 | " (Unit 84 | " , bind 85 | " , const) 86 | " ``` 87 | " 88 | " But it will run fine if all the lines are indented. 89 | fun! purescript#ide#import#lastImportLine(lines) 90 | let idx = len(a:lines) + 1 91 | let import = v:false 92 | for line in reverse(copy(a:lines)) 93 | let idx -= 1 94 | if line =~# '^import\>' 95 | break 96 | endif 97 | endfor 98 | let nLine = get(a:lines, idx+1, "-") 99 | while nLine =~# '^\s*$' || nLine =~# '^\s\+' 100 | let idx += 1 101 | let nLine = get(a:lines, idx, "-") 102 | endwhile 103 | return idx 104 | endfun 105 | 106 | " Import identifier callback 107 | " resp - server response 108 | " ident - identifier 109 | " view - win view (as returned by `winsaveview()`) 110 | " lines - number of lines in the buffer 111 | " silent - do not output any messages 112 | " rebuild - rebuild flag 113 | " ignoreMultiple 114 | " - ignore when received multiple results from the server 115 | " fixCol - when invoked from `CompleteDone` autocommand, we need to add one 116 | " to column. This works when one hits space to chose a completion 117 | " result, while it moves the cursor when is used (better than 118 | " the other way around). 119 | function! s:callback(resp, ident, view, lines, silent, rebuild, ignoreMultiple, fixCol) 120 | if type(a:resp) != v:t_dict || get(a:resp, "resultType", "error") !=# "success" 121 | if !a:silent && type(a:resp) == v:t_dict 122 | return purescript#ide#utils#log(a:resp["result"]) 123 | else 124 | return 125 | endif 126 | endif 127 | 128 | if type(a:resp.result) == v:t_list && type(get(a:resp.result, 0, v:null)) == v:t_dict 129 | " if v:false && type(a:resp.result) == v:t_list 130 | " multiple possibilities 131 | let respResults = a:resp.result 132 | if g:psc_ide_filter_prelude_modules && len(filter(copy(respResults), { idx, r -> r.module ==# "Prelude" })) 133 | " filter prelude modules (hopefully there are no identifires in prelude 134 | " that clash 135 | call s:FilterPrelude(respResults) 136 | endif 137 | if g:psc_ide_filter_submodules 138 | call s:FilterTop(respResults) 139 | endif 140 | let results = [] 141 | for res in respResults 142 | if empty(filter(copy(results), { idx, val -> val.module == res.module })) 143 | call add(results, res) 144 | endif 145 | endfor 146 | if (len(results) == 1) 147 | let choice = { "option": results[0], "picked": v:true } 148 | else 149 | if !a:ignoreMultiple 150 | let choice = purescript#ide#utils#pickOption("Multiple possibilities to import " . a:ident, results, "module") 151 | else 152 | return 153 | endif 154 | endif 155 | if choice.picked == v:true 156 | call purescript#ide#import#identifier(a:ident, choice.option.module) 157 | endif 158 | return 159 | endif 160 | 161 | let bLast = purescript#ide#import#lastImportLine(getline(1, '$')) 162 | let nLast = purescript#ide#import#lastImportLine(a:resp.result) 163 | exe "1," . bLast . "d_" 164 | call append(0, a:resp.result[0:nLast - 1]) 165 | 166 | if mode() == 'i' 167 | call feedkeys("\u", "n") 168 | endif 169 | let a:view.topline = a:view.topline + line("$") - a:lines 170 | let a:view.lnum = a:view.lnum + line("$") - a:lines 171 | if a:fixCol 172 | let a:view.col = a:view.col + 1 173 | endif 174 | call winrestview(a:view) 175 | if mode() == 'i' 176 | call feedkeys("\u", "n") 177 | endif 178 | 179 | " trigger PSCIDErebuild 180 | if a:rebuild 181 | call purescript#ide#utils#update() 182 | call PSCIDErebuild(v:true, "", function("PSCIDEerrors")) 183 | endif 184 | endfunction 185 | 186 | 187 | " import identifier 188 | " a:ident - the identifier (might be qualified) 189 | " a:module - empty string or name of the module to search in 190 | " 191 | " Explicit a:module is used when there were multiple choices, to limit the 192 | " choice, where it must be respected and also in 193 | " `purescript#ide#imports#completeDone` where it might be modified. 194 | function! purescript#ide#import#identifier(ident, module, ...) 195 | 196 | call purescript#ide#utils#debug('PSCIDEimportIdentifier', 3) 197 | call purescript#ide#utils#debug('ident: ' . a:ident, 3) 198 | 199 | if a:0 >= 1 200 | let silent = a:1 201 | else 202 | let silent = v:false 203 | endif 204 | 205 | if a:0 >= 2 206 | let rebuild = a:2 207 | else 208 | let rebuild = v:true 209 | endif 210 | 211 | if a:0 >= 3 212 | let ignoreMultiple = a:3 213 | else 214 | let ignoreMultiple = v:false 215 | endif 216 | 217 | if a:0 >= 4 218 | let fixCol = a:4 219 | else 220 | let fixCol = v:false 221 | endif 222 | 223 | if (a:ident == "") 224 | return 225 | endif 226 | 227 | if getline(".") =~ '^\s*import\>' 228 | return 229 | endif 230 | 231 | let file = fnamemodify(bufname(""), ":p") 232 | let [ident, qualifier] = purescript#ide#utils#splitQualifier(a:ident) 233 | if !empty(a:module) 234 | " When running through CompleteDone we need to preserve a:module. But 235 | " also the module might not be imported yet with qualificaton or the 236 | " qualified module was already imported in which case we'd limit the list 237 | " of modules to `a:module` anyway. 238 | let filters = [purescript#ide#utils#modulesFilter([a:module])] 239 | let importCommand = { 240 | \ "importCommand": "addImport", 241 | \ "identifier": ident 242 | \ } 243 | elseif empty(qualifier) 244 | let filters = [] 245 | let importCommand = { 246 | \ "importCommand": "addImport", 247 | \ "identifier": ident 248 | \ } 249 | else 250 | " Otherwise filter imported modules by qualification 251 | let currentModule = purescript#ide#utils#currentModule() 252 | let imports = purescript#ide#import#listImports(currentModule, qualifier) 253 | let modules = map(copy(imports), {key, val -> val["module"]}) 254 | if len(modules) > 0 255 | let filters = [purescript#ide#utils#modulesFilter(modules)] 256 | else 257 | let filters = [] 258 | endif 259 | let importCommand = { 260 | \ "importCommand": "addImport", 261 | \ "qualifier": qualifier, 262 | \ "identifier": ident 263 | \ } 264 | endif 265 | 266 | let input = { 267 | \ 'command': 'import' , 268 | \ 'params': { 269 | \ 'file': file, 270 | \ 'filters': filters, 271 | \ 'importCommand': importCommand 272 | \ } } 273 | 274 | if !empty(qualifier) 275 | let input.params.importCommand.qualifier = qualifier 276 | endif 277 | 278 | let view = winsaveview() 279 | let lines = line("$") 280 | " updated the file 281 | call purescript#ide#utils#update() 282 | 283 | call purescript#ide#call( 284 | \ input, 285 | \ silent ? v:null : "Failed to import identifier " . a:ident, 286 | \ 0, 287 | \ {resp -> s:callback(resp, a:ident, view, lines, silent, rebuild, ignoreMultiple, fixCol)}, 288 | \ v:true 289 | \ ) 290 | endfunction 291 | 292 | " Import identifiers on completion. This differs from the import command in 293 | " several ways: 294 | " - run in silent mode (do not disturb when a user is typing) 295 | " - do not rebuild when done 296 | " - ignore when there is no unique result 297 | " - add 1 to col after completion (see s:callback for explanation) 298 | fun! purescript#ide#import#completeDone() 299 | if !g:psc_ide_import_on_completion 300 | return 301 | endif 302 | 303 | if !has_key(v:completed_item, "word") 304 | return 305 | endif 306 | 307 | let ident = v:completed_item["word"] 308 | let module = get(split(v:completed_item["info"]), 0, "") 309 | call purescript#ide#import#identifier(ident, module, v:true, v:false, v:true, v:true) 310 | endfun 311 | -------------------------------------------------------------------------------- /autoload/purescript/ide/utils.vim: -------------------------------------------------------------------------------- 1 | " Find root folder ---------------------------------------------------- 2 | function! purescript#ide#utils#findRoot() 3 | let pscPackage = findfile("psc-package.json", fnameescape(expand("%:p:h")).";") 4 | if !empty(pscPackage) 5 | return fnamemodify(pscPackage, ":p:h") 6 | else 7 | let bower = findfile("bower.json", fnameescape(expand("%:p:h")).";") 8 | if !empty(bower) 9 | return fnamemodify(bower, ":p:h") 10 | else 11 | let spago = findfile("spago.dhall", fnameescape(expand("%:p:h")).";") 12 | if !empty(spago) 13 | return fnamemodify(spago, ":p:h") 14 | else 15 | return "" 16 | endif 17 | endif 18 | endfor 19 | endfunction 20 | 21 | fun! purescript#ide#utils#toString(msg) 22 | if type(a:msg) == v:t_string 23 | return a:msg 24 | elseif type(a:msg) == v:t_list 25 | return join(map(copy(a:msg), { idx, msg -> purescript#ide#utils#toString(msg) }), " ") 26 | elseif type(a:msg) == v:t_dict 27 | let msg = {} 28 | for key in a:msg 29 | let msg[key] = purescript#ide#utils#toString(a:msg[key]) 30 | endfor 31 | return string(msg) 32 | else 33 | return string(a:msg) 34 | endif 35 | endfun 36 | 37 | fun! purescript#ide#utils#error(msg, ...) 38 | let title = a:0 > 0 && a:1 ? "purs ide server: " : "purs ide: " 39 | echohl ErrorMsg 40 | echom title . join(split(a:msg, '\n'), ' ') 41 | echohl Normal 42 | endfun 43 | 44 | fun! purescript#ide#utils#warn(msg, ...) 45 | let title = a:0 > 0 && a:1 ? "purs ide server: " : "purs ide: " 46 | echohl WarningMsg 47 | echom title . join(split(a:msg, '\n'), ' ') 48 | echohl Normal 49 | endfun 50 | 51 | fun! purescript#ide#utils#log(msg, ...) 52 | let title = a:0 > 0 && a:1 ? "purs ide server: " : "purs ide: " 53 | echom title . join(split(a:msg, '\n'), ' ') 54 | endfun 55 | 56 | fun! purescript#ide#utils#debug(str, level) 57 | if g:psc_ide_log_level >= a:level 58 | echom a:str 59 | endif 60 | endfun 61 | 62 | fun! purescript#ide#utils#update() 63 | let ei=&ei 64 | set ei=all 65 | update 66 | let &ei=ei 67 | endfun 68 | 69 | fun! purescript#ide#utils#modulesFilter(modules) 70 | return { "filter": "modules", "params": { "modules": a:modules } } 71 | endfun 72 | 73 | " Display choices from a list of dicts for the user to select from with 74 | " alphanumerals as shortcuts 75 | function! purescript#ide#utils#pickOption(message, options, labelKey) 76 | let displayOptions = copy(a:options) 77 | call map(displayOptions, '(v:key > 9 ? nr2char(v:key + 55) : v:key) . " " . v:val[a:labelKey]') 78 | let choice = confirm(a:message, join(displayOptions, "\n")) 79 | if choice 80 | return {'picked': v:true, 'option': a:options[choice - 1]} 81 | else 82 | return {'picked': v:false, 'option': v:null} 83 | endif 84 | endfunction 85 | 86 | fun! purescript#ide#utils#splitQualifier(ident) 87 | if match(a:ident, '\.') != -1 88 | let str_ = split(a:ident, '\.', v:true) 89 | let qualifier = join(str_[0:len(str_)-2], ".") 90 | let ident= str_[len(str_) - 1] 91 | else 92 | let ident = a:ident 93 | let qualifier = "" 94 | endif 95 | return [ident, qualifier] 96 | endfun 97 | 98 | function! purescript#ide#utils#currentModule() 99 | " Find the module we're currently in. Don't know how to get the length of 100 | " the current buffer so just looking at the first 20 lines, should be enough 101 | let module = '' 102 | let iteration = 0 103 | while module == '' && iteration < 20 104 | let iteration += 1 105 | let line = getline(iteration) 106 | let matches = matchlist(line, 'module\s\(\S*\)') 107 | if len(matches) > 0 108 | let module = matches[1] 109 | endif 110 | endwhile 111 | 112 | return module 113 | endfunction 114 | -------------------------------------------------------------------------------- /autoload/purescript/job.vim: -------------------------------------------------------------------------------- 1 | " Author: Prabir Shrestha 2 | " License: The MIT License 3 | " Website: https://github.com/prabirshrestha/async.vim 4 | 5 | let s:save_cpo = &cpo 6 | set cpo&vim 7 | 8 | let s:jobidseq = 0 9 | let s:jobs = {} " { job, opts, type: 'vimjob|nvimjob'} 10 | let s:job_type_nvimjob = 'nvimjob' 11 | let s:job_type_vimjob = 'vimjob' 12 | let s:job_error_unsupported_job_type = -2 " unsupported job type 13 | 14 | function! s:job_supported_types() abort 15 | let l:supported_types = [] 16 | if has('nvim') 17 | let l:supported_types += [s:job_type_nvimjob] 18 | endif 19 | if !has('nvim') && has('job') && has('channel') && has('lambda') 20 | let l:supported_types += [s:job_type_vimjob] 21 | endif 22 | return l:supported_types 23 | endfunction 24 | 25 | function! s:job_supports_type(type) abort 26 | return index(s:job_supported_types(), a:type) >= 0 27 | endfunction 28 | 29 | function! s:out_cb(job, data, jobid, opts) abort 30 | if has_key(a:opts, 'on_stdout') 31 | call a:opts.on_stdout(a:jobid, split(a:data, "\n", 1), 'stdout') 32 | endif 33 | endfunction 34 | 35 | function! s:err_cb(job, data, jobid, opts) abort 36 | if has_key(a:opts, 'on_stderr') 37 | call a:opts.on_stderr(a:jobid, split(a:data, "\n", 1), 'stderr') 38 | endif 39 | endfunction 40 | 41 | function! s:exit_cb(job, status, jobid, opts) abort 42 | if has_key(a:opts, 'on_exit') 43 | call a:opts.on_exit(a:jobid, a:status, 'exit') 44 | endif 45 | if has_key(s:jobs, a:jobid) 46 | call remove(s:jobs, a:jobid) 47 | endif 48 | endfunction 49 | 50 | function! s:on_stdout(jobid, data, event) abort 51 | if has_key(s:jobs, a:jobid) 52 | let l:jobinfo = s:jobs[a:jobid] 53 | if has_key(l:jobinfo.opts, 'on_stdout') 54 | let l:stdout_chunks = s:jobs[a:jobid].stdout_chunks 55 | let l:stdout_chunks[-1] .= a:data[0] 56 | call extend(l:stdout_chunks, a:data[1:]) 57 | else 58 | echom "No on_stdout" 59 | endif 60 | endif 61 | endfunction 62 | 63 | function! s:on_stderr(jobid, data, event) abort 64 | if has_key(s:jobs, a:jobid) 65 | let l:jobinfo = s:jobs[a:jobid] 66 | if has_key(l:jobinfo.opts, 'on_stderr') 67 | call l:jobinfo.opts.on_stderr(a:jobid, a:data, a:event) 68 | endif 69 | endif 70 | endfunction 71 | 72 | function! s:on_exit(jobid, status, event) abort 73 | if has_key(s:jobs, a:jobid) 74 | let l:jobinfo = s:jobs[a:jobid] 75 | if has_key(l:jobinfo.opts, 'on_stdout') 76 | let l:stdout_chunks = s:jobs[a:jobid].stdout_chunks 77 | call l:jobinfo.opts.on_stdout(a:jobid, l:stdout_chunks, a:event) 78 | endif 79 | if has_key(l:jobinfo.opts, 'on_exit') 80 | call l:jobinfo.opts.on_exit(a:jobid, a:status, a:event) 81 | endif 82 | endif 83 | endfunction 84 | 85 | function! s:job_start(cmd, opts) abort 86 | let l:jobtypes = s:job_supported_types() 87 | let l:jobtype = '' 88 | 89 | if has_key(a:opts, 'type') 90 | if type(a:opts.type) == type('') 91 | if !s:job_supports_type(a:opts.type) 92 | return s:job_error_unsupported_job_type 93 | endif 94 | let l:jobtype = a:opts.type 95 | else 96 | let l:jobtypes = a:opts.type 97 | endif 98 | endif 99 | 100 | if empty(l:jobtype) 101 | " find the best jobtype 102 | for l:jobtype2 in l:jobtypes 103 | if s:job_supports_type(l:jobtype2) 104 | let l:jobtype = l:jobtype2 105 | endif 106 | endfor 107 | endif 108 | 109 | if l:jobtype == '' 110 | return s:job_error_unsupported_job_type 111 | endif 112 | 113 | if l:jobtype == s:job_type_nvimjob 114 | let l:job = jobstart(a:cmd, { 115 | \ 'on_stdout': function('s:on_stdout'), 116 | \ 'on_stderr': function('s:on_stderr'), 117 | \ 'on_exit': function('s:on_exit'), 118 | \}) 119 | if l:job <= 0 120 | return l:job 121 | endif 122 | let l:jobid = l:job " nvimjobid and internal jobid is same 123 | let s:jobs[l:jobid] = { 124 | \ 'type': s:job_type_nvimjob, 125 | \ 'opts': a:opts, 126 | \ 'stdout_chunks': [''], 127 | \ } 128 | let s:jobs[l:jobid].job = l:job 129 | elseif l:jobtype == s:job_type_vimjob 130 | let s:jobidseq = s:jobidseq + 1 131 | let l:jobid = s:jobidseq 132 | let l:job = job_start(a:cmd, { 133 | \ 'out_cb': {job,data->s:out_cb(job, data, l:jobid, a:opts)}, 134 | \ 'err_cb': {job,data->s:err_cb(job, data, l:jobid, a:opts)}, 135 | \ 'exit_cb': {job,data->s:exit_cb(job, data, l:jobid, a:opts)}, 136 | \ 'mode': 'raw', 137 | \}) 138 | if job_status(l:job) != 'run' 139 | return -1 140 | endif 141 | let s:jobs[l:jobid] = { 142 | \ 'type': s:job_type_vimjob, 143 | \ 'opts': a:opts, 144 | \ 'job': l:job, 145 | \ 'channel': job_getchannel(l:job) 146 | \ } 147 | else 148 | return s:job_error_unsupported_job_type 149 | endif 150 | 151 | return l:jobid 152 | endfunction 153 | 154 | function! s:job_stop(jobid) abort 155 | if has_key(s:jobs, a:jobid) 156 | let l:jobinfo = s:jobs[a:jobid] 157 | if l:jobinfo.type == s:job_type_nvimjob 158 | call jobstop(a:jobid) 159 | elseif l:jobinfo.type == s:job_type_vimjob 160 | call job_stop(s:jobs[a:jobid].job) 161 | endif 162 | if has_key(s:jobs, a:jobid) 163 | call remove(s:jobs, a:jobid) 164 | endif 165 | endif 166 | endfunction 167 | 168 | function! s:job_send(jobid, data) abort 169 | let l:jobinfo = s:jobs[a:jobid] 170 | if l:jobinfo.type == s:job_type_nvimjob 171 | return jobsend(a:jobid, a:data) 172 | elseif l:jobinfo.type == s:job_type_vimjob 173 | return ch_sendraw(l:jobinfo.channel, a:data) 174 | endif 175 | endfunction 176 | 177 | " public apis {{{ 178 | function! purescript#job#start(cmd, opts) abort 179 | return s:job_start(a:cmd, a:opts) 180 | endfunction 181 | 182 | function! purescript#job#stop(jobid) abort 183 | call s:job_stop(a:jobid) 184 | endfunction 185 | 186 | function! purescript#job#send(jobid, data) abort 187 | return s:job_send(a:jobid, a:data) 188 | endfunction 189 | " }}} 190 | -------------------------------------------------------------------------------- /doc/psc-ide-vim.txt: -------------------------------------------------------------------------------- 1 | PureScript psc ide integration *psc-ide-vim* *purs* 2 | 3 | COMPLETION *purs-completion* 4 | The plugin provides omni completion |i_CTRL-X_CTRL-O| for your PureScript 5 | coding and user completion so you can complete with |i_CTRL-X_CTRL-U| too. 6 | The 'omnifunc' is though to provide as thigh list of results as possible, 7 | while the user completion is though to find as many results as possible. 8 | 9 | Both completion function will work with qualified and unqualified 10 | indentifiers. 11 | 12 | Also many of the commands can be completed. 13 | 14 | PURS SERVER COMMANDS *purs-server-commands* 15 | 16 | *:Plist* 17 | > 18 | :Plist 19 | 22 | :Pload[!] 23 | |, first reset loaded modules. 24 | 25 | *:Pcwd* 26 | > 27 | :Pcwd 28 | 31 | :Pend 32 | 35 | :Prebuild[!] 36 | | first reload the modules. 37 | 38 | EDITING COMMANDS *purs-editing-commands* 39 | 40 | *:PaddClause* 41 | > 42 | :PaddClause 43 | 47 | :PaddType 48 | 51 | concat :: forall a b. Show a => Show b => a -> b -> String 52 | < 53 | It will produce: 54 | > 55 | concat :: forall a b. Show a => Show b => a -> b -> String 56 | concat _ _ = ?concat 57 | < 58 | 59 | *:Papply* 60 | > 61 | :Papply[!] 62 | | applies all 63 | suggestions. Warning that have suggestion are indicated with 'V' in the quick 64 | fix list. 65 | 66 | *:Pcase* 67 | > 68 | :Pcase[!] type 69 | | it will also 70 | include type annotations, e.g. starting with 71 | > 72 | myCase :: Either String Int -> String 73 | myCase _ = ?s< -- cursor over `_` 74 | 75 | then using `:Pcase Either String Int` and will put 76 | > 77 | myCase :: Either String Int -> String 78 | myCase (Left _) = ?s 79 | myCase (Right _) = ?s 80 | 82 | myCase :: Either String Int -> String 83 | myCase a = case a of 84 | _ -> ?s -- cursor over `_` 85 | 87 | myCase :: Either String Int -> String 88 | myCase a = case a of 89 | (Left _) -> ?s 90 | (Right _) -> ?s 91 | < 92 | IMPORT COMMANDS *purs-import-commands* 93 | 94 | *:Pimport* 95 | > 96 | :Pimport [ident] 97 | 105 | :PimportModule ident [qualificier] 106 | 109 | :Pimports 110 | 114 | :PaddImportQualifications 115 | < 116 | 117 | SEARCH COMMANDS *purs-search-commands* 118 | 119 | *:Pgoto* 120 | > 121 | :Pgoto [ident] 122 | 127 | :Pursuit [ident] 128 | 132 | :Ptype[!] [ident] 133 | 138 | :Psearch ident 139 |