~gpanders/dotfiles

ref: 2da1dde5ea566f82ebe6532bc6d3b8670fd77ce8 dotfiles/vim/.vim/autoload/async.vim -rw-r--r-- 4.0 KiB
2da1dde5Greg Anders Note 'open' subcommand opens editor in NOTES_DIR when no argument given 1 year, 3 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
" Wrapper functions for asynchronous APIs for nvim and vim
" Author: Greg Anders
" Date: 2019-04-16

" Neovim and Vim 8 both have a jobs API, but the syntax for each is slightly
" different. This function aims to provide a consistent interface for both to
" be used by other plugins throughout (n)vim.

let s:jobs = {}

function! s:callback(id, msg)
  let msg = a:msg
  let job = s:jobs[a:id]
  if type(job.cb) == type({-> 1})
    call job.cb(msg)
  elseif type(job.cb) == type('')
    if exists('*' . job.cb)
      let F = function(job.cb)
      call F(msg)
    else
      if type(msg) == type([])
        let msg = join(msg, "\n")
      endif
      " Apparently the substitute() function removes escaped characters, so
      " for example \" becomes ", so we have to escape the escaped
      " characters, hence the `escape(..., '\')` in the line below
      let command = substitute(job.cb, '\C\<v:val\>', '"' . escape(fnameescape(msg), '\') . '"', 'g')
      " After double escaping (explained above) the newline characters are
      " represented as \\n (a backslash followed by an actual newline). The
      " following replaces these with the literal '\n' character
      execute join(split(command, '\\\n'), '\n')
    endif
  endif
endfunction

function! s:stdout(channel, msg, ...)
  if a:0
    let id = a:1
  else
    let id = ch_info(a:channel).id
  endif

  " Make sure job is still available
  if has_key(s:jobs, id)
    let job = s:jobs[id]
    let msg = a:msg
    if type(msg) == type('')
      let msg = split(msg, "\n", 1)
    endif
    let job.chunks[-1] .= msg[0]
    call extend(job.chunks, msg[1:])
    if !job.buffered && len(job.chunks) > 1
      call s:callback(id, remove(job.chunks, 0, -2))
    end
  endif
endfunction

function! s:error(channel, msg, ...)
  echohl ErrorMsg
  let msg = a:msg
  if type(msg) == type([])
    let msg = join(msg[:-2])
  endif
  echom msg
  echohl None
endfunction

function! s:exit(channel, msg, ...)
  if a:0
    let id = a:1
  else
    let id = ch_info(a:channel).id
  endif

  let job = s:jobs[id]
  if job.buffered
    if job.chunks[-1] ==# ''
      call remove(job.chunks, -1)
    endif
    call s:callback(id, job.chunks)
  endif
  call job.completed(a:msg)
  call remove(s:jobs, id)
endfunction

function! s:shellsplit(str)
  return map(split(a:str, '\%(^\%("[^"]*"\|[^"]\)*\)\@<= '), {_, v -> substitute(v, '^"\|"$', '', 'g')})
endfunction

if has('nvim')
  let s:opts = {
        \ 'on_stdout': {i, d, e -> s:stdout(e, d, i)},
        \ 'on_stderr': {i, d, e -> s:error(e, d, i)},
        \ 'on_exit': {i, d, e -> s:exit(e, d, i)},
        \ 'stderr_buffered': 1,
        \ }
else
  let s:opts = {
        \ 'out_cb': function('s:stdout'),
        \ 'err_cb': function('s:error'),
        \ 'exit_cb': function('s:exit'),
        \ 'in_io': 'null',
        \ 'out_mode': 'raw'
        \ }
endif

function! async#run(cmd, cb, ...)
  let cmd = a:cmd

  let opts = {}
  if a:0
    let opts = a:1
  endif

  if get(opts, 'shell', 0) " Run command in a subshell
    " Convert command into a string if it is in list form
    if type(cmd) == type([])
      let cmd = join(cmd)
    endif

    if !has('nvim')
      " Neovim's jobstart() uses 'shell' by default when the command argument
      " is a string. For Vim, we have to explicitly add the shell command part
      let cmd = split(&shell) + split(&shellcmdflag) + [cmd]
    endif
  elseif type(cmd) == type('')
    " If the 'shell' option is not specified and the cmd argument is a string,
    " convert it into a list
    let cmd = s:shellsplit(cmd)
  endif

  if has('nvim')
    let jobid = jobstart(cmd, s:opts)
  elseif has('job')
    let job = job_start(cmd, s:opts)
    let jobid = ch_info(job_info(job).channel).id
  else
    echohl ErrorMsg
    echom 'Jobs API not supported'
    echohl None
    return
  endif

  let s:jobs[jobid] = {
        \ 'cb': a:cb,
        \ 'chunks': [''],
        \ 'buffered': get(opts, 'buffered', 1),
        \ 'completed': get(opts, 'completed', {_ -> 0}),
        \ }
  return jobid
endfunction