Skip to content

Commit e7c5c65

Browse files
committed
Merge branch 'feature/nvim-compatibility'
2 parents 7ff53f0 + 26edb91 commit e7c5c65

File tree

4 files changed

+139
-55
lines changed

4 files changed

+139
-55
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ favorite code editor.
77

88
## Requirements
99
This plugin relies on [channels](https://vimhelp.org/channel.txt.html), introduced in Vim 8.0.
10-
This is not yet supported in Neovim because of its different implementation of
10+
This is only partially supported in Neovim because of its different implementation of
1111
[job control](https://neovim.io/doc/user/job_control.html).
1212

1313
## Introduction

autoload/run.vim

Lines changed: 127 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ let s:run_last_command = get(s:, 'run_last_command', '')
99
let s:run_last_options = get(s:, 'run_last_options', {})
1010
let s:run_killall_ongoing = get(s:, 'run_killall_ongoing', 0)
1111
let s:run_timestamp_format = get(s:, 'run_timestamp_format', '%Y-%m-%d %H:%M:%S')
12+
let s:run_jobs_to_kill_nvim = get(s:, 'run_jobs_to_kill_nvim', [])
1213

1314
let s:run_edit_path = get(s:, 'run_edit_path')
1415
let s:run_edit_cmd_ongoing = get(s:, 'run_edit_cmd_ongoing', 0)
@@ -30,11 +31,15 @@ augroup RunCmdBufInput
3031
let rundirglob = g:rundir . '/*.log'
3132

3233
autocmd!
33-
exec 'autocmd BufWinEnter ' . [editglob, sendglob, rundirglob]->join(',')
34+
let wipe_bufs = [editglob, sendglob, rundirglob]
35+
if has('nvim')
36+
call remove(wipe_bufs, -1)
37+
endif
38+
exec 'autocmd BufWinEnter ' . join(wipe_bufs, ',')
3439
\ . ' setlocal bufhidden=wipe'
35-
exec 'autocmd BufWinEnter ' . [rundirglob, tempglob]->join(',')
40+
exec 'autocmd BufWinEnter ' . join([rundirglob, tempglob], ',')
3641
\ . ' setlocal ft=log'
37-
exec 'autocmd BufWinEnter ' . [rundirglob, tempglob]->join(',')
42+
exec 'autocmd BufWinEnter ' . join([rundirglob, tempglob], ',')
3843
\ . ' setlocal noma'
3944
exec 'autocmd BufWinLeave ' . editglob . ' call run#cmd_input_finished()'
4045
exec 'autocmd BufWinLeave ' . sendglob . ' call run#cmd_input_finished({"send":1})'
@@ -49,6 +54,14 @@ endif
4954
" main functions
5055
function! run#Run(cmd, ...) abort
5156
let options = get(a:, 1, {})
57+
if has('nvim')
58+
let options['nostream'] = 1
59+
if get(options, 'split') || get(options, 'vsplit')
60+
call run#print_formatted('ErrorMsg',
61+
\ 'Streaming output to a buffer is not supported in Neovim.')
62+
return
63+
endif
64+
endif
5265

5366
" finish editing first
5467
if s:run_edit_cmd_ongoing
@@ -86,7 +99,7 @@ function! run#Run(cmd, ...) abort
8699
let s:run_edit_path = s:run_cmd_path . 'edit-' . timestamp . '.sh'
87100
let editor_lines = [s:edit_msg, '']
88101
if get(options, 'edit_last')
89-
call extend(editor_lines, s:run_last_command->split("\n"))
102+
call extend(editor_lines, split(s:run_last_command, "\n"))
90103
else
91104
call add(editor_lines, '')
92105
endif
@@ -106,19 +119,20 @@ function! run#Run(cmd, ...) abort
106119

107120
" run job as shell command to tempfile w/ details
108121
let date_cmd = 'date +"' . s:run_timestamp_format . '"'
109-
call writefile(a:cmd->split("\n"), currentcmdpath)
122+
let startline = has('nvim') ? 1 : 2
123+
call writefile(split(a:cmd, "\n"), currentcmdpath)
110124
call writefile([
111125
\ 'printf "COMMAND: "',
112-
\ 'cat ' . currentcmdpath . " | sed '2,${s/^/ /g}'",
126+
\ 'cat ' . currentcmdpath . " | sed '".startline.",${s/^/ /g}'",
113127
\ 'echo WORKDIR: ' . getcwd(),
114-
\ 'printf "STARTED: "',
115-
\ date_cmd,
128+
\ 'STARTED=$('.date_cmd.')',
129+
\ 'echo "STARTED: $STARTED"',
116130
\ 'printf "\n"',
117131
\ g:run_shell . ' ' . currentcmdpath,
118132
\ 'EXITVAL=$?',
119133
\ 'STATUS=$([ $EXITVAL -eq 0 ] && echo "FINISHED" || echo "FAILED (status $EXITVAL)")',
120-
\ 'printf "\n$STATUS: "',
121-
\ date_cmd,
134+
\ 'FINISHED=$('.date_cmd.')',
135+
\ 'echo "\n$STATUS: $FINISHED"',
122136
\ 'exit $EXITVAL',
123137
\ ], execpath)
124138

@@ -143,13 +157,28 @@ function! run#Run(cmd, ...) abort
143157
let ext_options['out_cb'] = 'run#out_cb'
144158
endif
145159
endif
160+
161+
" nvim overrides
162+
if has('nvim')
163+
let ext_options = {}
164+
let ext_options['on_exit'] = 'run#close_cb'
165+
let ext_options['on_stdout'] = 'run#out_cb'
166+
let ext_options['out_name'] = fpath
167+
" let ext_options['stdout_buffered'] = v:true
168+
endif
169+
146170
call extend(job_options, ext_options)
147-
let job = job_start([g:run_shell, execpath]->join(' '), job_options)
171+
if has('nvim')
172+
let job = jobstart(join([g:run_shell, execpath], ' '), job_options)
173+
let pid = job
174+
else
175+
let job = job_start(join([g:run_shell, execpath], ' '), job_options)
176+
let pid = job_info(job)['process']
177+
endif
148178

149179
" get job info for global job dict
150-
let info = job_info(job)
151180
let job_obj = {
152-
\ 'pid': info['process'],
181+
\ 'pid': pid,
153182
\ 'command': a:cmd,
154183
\ 'bufname': (is_nostream ? fpath : temppath),
155184
\ 'filename': fpath,
@@ -213,6 +242,11 @@ function! run#RunAgainEdit() abort
213242
endfunction
214243

215244
function! run#RunSendKeys(cmd, ...) abort
245+
if has('nvim')
246+
call run#print_formatted('ErrorMsg',
247+
\ 'Sending keys to a running job is not supported in Neovim.')
248+
return
249+
endif
216250
let options = get(a:, 1, {})
217251

218252
" finish editing first
@@ -248,7 +282,7 @@ function! run#RunSendKeys(cmd, ...) abort
248282
let timestamp = job_info['timestamp']
249283
let editor_lines = [s:edit_msg, '', '']
250284

251-
if empty(a:cmd->trim())
285+
if empty(trim(a:cmd))
252286
if is_from_editor
253287
call run#print_formatted('WarningMsg', 'User cancelled command input.')
254288
else
@@ -290,14 +324,18 @@ function! run#RunKill(...) abort
290324
endif
291325
return 0
292326
else
293-
call job_stop(job['job'], 'kill')
327+
if has('nvim')
328+
call jobstop(job['job'])
329+
else
330+
call job_stop(job['job'], 'kill')
331+
endif
294332
return 1
295333
endif
296334
endfunction
297335

298336
function! run#RunKillAll() abort
299337
" user confirm
300-
let running_jobs = run#list_running_jobs()->split("\n")
338+
let running_jobs = split(run#list_running_jobs(), "\n")
301339
if empty(running_jobs)
302340
call run#print_formatted('WarningMsg', 'No jobs are running.')
303341
return
@@ -307,8 +345,19 @@ function! run#RunKillAll() abort
307345
if confirm !=? 'Y'
308346
return
309347
endif
348+
349+
" in case user confirms late and jobs have stopped
350+
let running_jobs = split(run#list_running_jobs(), "\n")
351+
if empty(running_jobs)
352+
call run#print_formatted('WarningMsg', 'All jobs have finished running.')
353+
return
354+
endif
355+
310356
let s:run_killed_jobs = 0
311357
let s:run_killall_ongoing = len(running_jobs)
358+
if has('nvim')
359+
let s:run_jobs_to_kill_nvim = running_jobs
360+
endif
312361
for job_key in running_jobs
313362
call run#RunKill(job_key)
314363
endfor
@@ -330,18 +379,20 @@ function! run#RunClear(status_list) abort
330379
endif
331380
" user confirm
332381
let confirm = input(
333-
\ 'Clear all jobs with status ' . a:status_list->join('/') . '? (Y/n) '
382+
\ 'Clear all jobs with status ' . join(a:status_list, '/') . '? (Y/n) '
334383
\ )
335384
if confirm !=? 'Y'
336385
return
337386
endif
338387

339388
" remove all jobs that match status_list
340389
let clear_count = 0
341-
for job in s:run_jobs->values()
342-
let status_match = a:status_list->index(job['status']) >= 0
390+
for job in values(s:run_jobs)
391+
let status_match = index(a:status_list, job['status']) >= 0
343392
if status_match
344-
silent exec 'bw! ' . job['bufname']
393+
if get(job, 'bufname')
394+
silent exec 'bw! ' . job['bufname']
395+
endif
345396
unlet s:run_jobs[job['timestamp']]
346397
let clear_count += 1
347398
endif
@@ -411,12 +462,12 @@ function! run#RunBrowseLogs(...) abort
411462
\ " | xargs -n 1 -I FILE" .
412463
\ " sh -c 'printf \"FILE \" && echo $(head -1 FILE)'"
413464
let qf_output = []
414-
for entry in system(cmd_get_files)->trim()->split("\n")
465+
for entry in split(trim(system(cmd_get_files)), "\n")
415466
let qf_item = {}
416467
let split_str = ' COMMAND: '
417-
let split_cmd = entry->split(split_str)
468+
let split_cmd = split(entry, split_str)
418469
let qf_item['filename'] = split_cmd[0]
419-
let qf_item['text'] = 'SAVED - ' . split_cmd[1:]->join(split_str)
470+
let qf_item['text'] = 'SAVED - ' . join(split_cmd[1:], split_str)
420471
call add(qf_output, qf_item)
421472
endfor
422473

@@ -442,7 +493,7 @@ function! run#RunDeleteLogs() abort
442493
endif
443494
call system('rm ' . g:rundir . '/*.log')
444495

445-
let qf_title = getqflist({'title': 1})->get('title')
496+
let qf_title = get(getqflist({'title': 1}), 'title')
446497
if run#is_qf_open() && qf_title ==# 'RunLogs'
447498
silent call setqflist([])
448499
silent call setqflist([], 'a', {'title': 'RunLogs'})
@@ -478,9 +529,9 @@ function! run#cmd_input_finished(...)
478529
silent exec win . 'wincmd w'
479530

480531
" keep only non-comment lines w/ text, join to one line
481-
let cmd_text = getline(1, '$')
482-
\ ->filter('v:val->trim() !~ "^#" && len(v:val->trim()) > 0')
483-
\ ->join("\n")
532+
let cmd_text = join(filter(getline(1, '$'),
533+
\ 'trim(v:val) !~ "^#" && len(trim(v:val)) > 0'),
534+
\ "\n")
484535

485536
call extend(s:run_edit_options, {'is_from_editor': 1})
486537
if !get(options, 'send')
@@ -499,7 +550,7 @@ function! run#get_current_buf_job(...)
499550
let options = get(a:, 1, {})
500551
let curr = bufname('%')
501552

502-
for job_info in s:run_jobs->values()
553+
for job_info in values(s:run_jobs)
503554
if job_info['bufname'] ==# curr
504555
return job_info['job']
505556
endif
@@ -508,28 +559,22 @@ endfunction
508559

509560
function! run#update_run_jobs()
510561
let qf_output = []
511-
let run_jobs_sorted = reverse(sort(s:run_jobs->values(), {
562+
let run_jobs_sorted = reverse(sort(values(s:run_jobs), {
512563
\ v1, v2 -> v1.timestamp ==# v2.timestamp ? 0
513564
\ : v1.timestamp > v2.timestamp ? 1 : -1
514565
\ }))
515566
for val in run_jobs_sorted
516567
let qf_item = {}
517-
let status = job_status(val['job'])
518568
let is_nostream = get(val['options'], 'nostream')
519569

520570
" set the qf buffer / file to open
521-
if is_nostream || (status !=# 'run' && val['save'])
571+
let status = val['status']
572+
if is_nostream || (status !=# 'RUNNING' && val['save'])
522573
let qf_item['filename'] = val['filename']
523574
else
524575
let qf_item['bufnr'] = bufnr(val['bufname'])
525576
endif
526577
" set the qf message (status)
527-
if status ==# 'run'
528-
let status = 'RUNNING'
529-
else
530-
let exitval = job_info(val['job'])['exitval']
531-
let status = exitval ==# 0 ? 'DONE' : exitval ==# -1 ? 'KILLED' : 'FAILED'
532-
endif
533578
let qf_item['text'] = status . ' - ' . val['command']
534579

535580
" update output and global jobs dict
@@ -556,22 +601,26 @@ function! run#alert_and_update(content, ...)
556601
endfunction
557602

558603
function! run#get_job_with_object(job)
559-
let pid = job_info(a:job)['process']
560-
for job in s:run_jobs->values()
604+
let pid = has('nvim') ? a:job : job_info(a:job)['process']
605+
for job in values(s:run_jobs)
561606
if job['pid'] ==# pid
562607
return job
563608
endif
564609
endfor
565610
endfunction
566611

567612
function! run#list_running_jobs(...)
568-
return deepcopy(s:run_jobs)->filter('v:val.status ==# "RUNNING"')
569-
\ ->keys()->join("\n")
613+
return join(keys(
614+
\ filter(deepcopy(s:run_jobs),
615+
\ 'v:val.status ==# "RUNNING"')
616+
\ ), "\n")
570617
endfunction
571618

572619
function! run#list_unsaved_jobs(...)
573-
return deepcopy(s:run_jobs)->filter('v:val.save ==# 0 && v:val.status !=# "RUNNING"')
574-
\ ->keys()->join("\n")
620+
return join(keys(
621+
\ filter(deepcopy(s:run_jobs),
622+
\ 'v:val.save ==# 0 && v:val.status !=# "RUNNING"')
623+
\ ), "\n")
575624
endfunction
576625

577626
function! run#is_qf_open()
@@ -589,17 +638,43 @@ endfunction
589638

590639

591640
" callbacks
592-
function! run#out_cb(channel, msg)
641+
function! run#out_cb(channel, msg, ...)
593642
" write logs to output file while running
594-
let job = run#get_job_with_object(ch_getjob(a:channel))
643+
let job_obj = has('nvim') ? a:channel : ch_getjob(a:channel)
644+
let job = run#get_job_with_object(job_obj)
595645
let fname = job['filename']
596-
call writefile([a:msg], fname, "a")
646+
if has('nvim')
647+
let output = a:msg
648+
if empty(trim(output[-1]))
649+
let output = output[:-2]
650+
endif
651+
else
652+
let output = [a:msg]
653+
endif
654+
call writefile(output, fname, "a")
597655
endfunction
598656

599-
function! run#close_cb(channel)
600-
let job = ch_getjob(a:channel)
601-
let info = run#get_job_with_object(job)
602-
let exitval = job_info(info['job'])['exitval']
657+
function! run#close_cb(channel, ...)
658+
if has('nvim')
659+
let job = a:channel
660+
let info = run#get_job_with_object(job)
661+
" nvim exitval is unreliable
662+
let kill_idx = index(s:run_jobs_to_kill_nvim, info['timestamp'])
663+
let exitval = get(a:, 1, 0)
664+
if kill_idx > -1
665+
let exitval = -1
666+
call remove(s:run_jobs_to_kill_nvim, kill_idx)
667+
endif
668+
else
669+
let job = ch_getjob(a:channel)
670+
let info = run#get_job_with_object(job)
671+
let exitval = job_info(info['job'])['exitval']
672+
endif
673+
674+
" calculate status here
675+
let status = (exitval == -1) ? 'KILLED' : (exitval == 0) ? 'DONE' : 'FAILED'
676+
let s:run_jobs[info['timestamp']]['exitval'] = exitval
677+
let s:run_jobs[info['timestamp']]['status'] = status
603678

604679
" if saved and window unfocused, wipe temp buffer
605680
let bufexists = bufnr(info['bufname']) !=# -1
@@ -617,8 +692,9 @@ function! run#close_cb(channel)
617692
let s:run_killed_jobs += 1
618693

619694
" killall finished
620-
if s:run_killed_jobs ==# s:run_killall_ongoing
695+
if len(run#list_running_jobs()) == 0
621696
let s:run_killall_ongoing = 0
697+
let s:run_jobs_to_kill_nvim = []
622698
let msg = s:run_killed_jobs .
623699
\ (s:run_killed_jobs !=# 1 ? ' jobs killed.' : ' job killed.')
624700
call run#alert_and_update(msg, kill_options)

doc/vim-run.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ experience around running processess asynchronously.
3737
==============================================================================
3838
REQUIREMENTS *vim-run-requirements*
3939

40-
This plugin relies on |channel|s, introduced in Vim 8.0. This is currently not
41-
yet supported in Neovim because of its different implementation of job
42-
control.
40+
This plugin relies on |channel|s, introduced in Vim 8.0. This is only
41+
partially supported in Neovim because of its different implementation of job
42+
control (for nvim users, see |job-control|).
4343

4444
==============================================================================
4545
COMMANDS *vim-run-commands*

0 commit comments

Comments
 (0)