1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-01-23 04:00:03 +08:00

feat(runner): rewrite code runner in lua

This commit is contained in:
Eric Wong 2023-07-13 00:00:35 +08:00 committed by GitHub
parent 69cddda857
commit 8b55955de4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 618 additions and 12 deletions

View File

@ -0,0 +1,26 @@
-- Return an item that represents a time value. The item is a
-- list with items that depend on the system.
-- The item can be passed to `reltimestr()` to convert it to a
-- string or `reltimefloat()` to convert to a Float.
--
-- Without an argument it returns the current "relative time", an
-- implementation-defined value meaningful only when used as an
-- argument to `reltime()`, `reltimestr()` and `reltimefloat()`.
--
-- With one argument it returns the time passed since the time
-- specified in the argument.
-- With two arguments it returns the time passed between {start}
-- and {end}.
--
-- The {start} and {end} arguments must be values returned by
-- reltime(). Returns zero on error.
--
-- Can also be used as a `method`:
-- ```vim
-- GetStart()->reltime()
-- ```
-- Note: `localtime()` returns the current (non-relative) time.
--- @param start? any[]
--- @param end_? any[]
--- @return any[]
function vim.fn.reltime(start, end_) end

View File

@ -36,6 +36,62 @@
" }
" <
if has('nvim-0.9.0') && $USE_LUA_CODE_RUNEER == 1
function! SpaceVim#plugins#runner#get(ft) abort
return luaeval('require("spacevim.plugin.runner").get(require("spacevim").eval("a:ft"))')
endfunction
function! SpaceVim#plugins#runner#open(...) abort
lua require("spacevim.plugin.runner").open(
\ unpack(require("spacevim").eval("a:000"))
\ )
endfunction
function! SpaceVim#plugins#runner#reg_runner(ft, runner) abort
lua require("spacevim.plugin.runner").reg_runner(
\ require("spacevim").eval("a:ft"),
\ require("spacevim").eval("a:runner")
\ )
endfunction
function! SpaceVim#plugins#runner#status() abort
return luaeval('require("spacevim.plugin.runner").status()')
endfunction
function! SpaceVim#plugins#runner#close() abort
lua require("spacevim.plugin.runner").close()
endfunction
function! SpaceVim#plugins#runner#select_file() abort
lua require("spacevim.plugin.runner").select_file()
endfunction
function! SpaceVim#plugins#runner#select_language() abort
lua require("spacevim.plugin.runner").select_language()
endfunction
function! SpaceVim#plugins#runner#set_language(lang) abort
endfunction
function! SpaceVim#plugins#runner#run_task(task) abort
endfunction
function! SpaceVim#plugins#runner#clear_tasks() abort
endfunction
finish
endif
let s:runners = {}
let s:JOB = SpaceVim#api#import('job')
@ -74,7 +130,8 @@ let s:task_stderr = {}
let s:task_problem_matcher = {}
function! s:open_win() abort
if s:code_runner_bufnr !=# 0 && bufexists(s:code_runner_bufnr) && index(tabpagebuflist(), s:code_runner_bufnr) !=# -1
if s:code_runner_bufnr !=# 0 && bufexists(s:code_runner_bufnr)
\ && index(tabpagebuflist(), s:code_runner_bufnr) !=# -1
return
endif
botright split __runner__
@ -281,15 +338,13 @@ function! SpaceVim#plugins#runner#open(...) abort
\ 'has_errors' : 0,
\ 'exit_code' : 0
\ }
let s:selected_language = &filetype
let runner = get(a:000, 0, get(s:runners, s:selected_language, ''))
let selected_language = &filetype
let runner = get(a:000, 0, get(s:runners, selected_language, ''))
let opts = get(a:000, 1, {})
if !empty(runner)
call s:open_win()
call s:async_run(runner, opts)
call s:update_statusline()
else
let s:selected_language = get(s:, 'selected_language', '')
endif
endfunction

View File

@ -2,10 +2,9 @@
snippet func
abbr function name(args)...end
options word
function ${1:#:function_name}(${2:#:argument}) -- {{{
function ${1:#:function_name}(${2:#:argument})
${0:TARGET}
end
-- }}}
snippet if
options head

View File

@ -0,0 +1,516 @@
--=============================================================================
-- M.lua
-- Copyright (c) 2016-2022 Wang Shidong & Contributors
-- Author: Wang Shidong < wsdjeg@outlook.com >
-- URL: https://spacevim.org
-- License: GPLv3
--=============================================================================
local M = {}
local runners = {}
local logger = require('spacevim.logger').derive('runner')
local job = require('spacevim.api').import('job')
local file = require('spacevim.api').import('file')
local str = require('spacevim.api').import('data.string')
local code_runner_bufnr = 0
local winid = -1
local target = ''
local runner_lines = 0
local runner_jobid = 0
local runner_status = {
is_running = false,
has_errors = false,
exit_code = 0,
exit_single = 0,
}
local selected_file = ''
--- @type any[]
local start_time
--- @type any[]
local end_time
local task_status = {}
local task_stdout = {}
local task_stderr = {}
local task_problem_matcher = {}
local selected_language = ''
local function stop_runner()
if runner_status.is_running then
logger.debug('stop runner:' .. runner_jobid)
job.stop(runner_jobid)
end
end
local function close_win()
stop_runner()
if code_runner_bufnr ~= 0 and vim.api.nvim_buf_is_valid(code_runner_bufnr) then
vim.cmd('bd ' .. code_runner_bufnr)
end
end
local function insert()
vim.fn.inputsave()
local input = vim.fn.input('input >')
if vim.fn.empty(input) == 0 and runner_status.is_running then
job.send(runner_jobid, input)
end
vim.cmd('normal! :')
vim.fn.inputrestore()
end
local function open_win()
if
code_runner_bufnr ~= 0
and vim.api.nvim_buf_is_valid(code_runner_bufnr)
and vim.fn.index(vim.fn.tabpagebuflist(), code_runner_bufnr) ~= -1
then
return
end
logger.debug('open code runner windows')
local previous_wind = vim.api.nvim_get_current_win()
vim.cmd('botright split __runner__')
code_runner_bufnr = vim.fn.bufnr('%')
local lines = vim.o.lines * 30 / 100
vim.cmd('resize ' .. lines)
vim.cmd([[
setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline nospell nonu norelativenumber winfixheight nomodifiable
set filetype=SpaceVimRunner
]])
vim.api.nvim_buf_set_keymap(code_runner_bufnr, 'n', 'q', '', {
callback = close_win,
})
vim.api.nvim_buf_set_keymap(code_runner_bufnr, 'n', 'i', '', {
callback = insert,
})
vim.api.nvim_buf_set_keymap(code_runner_bufnr, 'n', '<C-c>', '', {
callback = stop_runner,
})
local id = vim.api.nvim_create_augroup('spacevim_runner', {
clear = true,
})
vim.api.nvim_create_autocmd({ 'BufWipeout' }, {
group = id,
buffer = code_runner_bufnr,
callback = stop_runner,
})
winid = vim.api.nvim_get_current_win()
if vim.g.spacevim_code_runner_focus == 0 then
vim.api.nvim_set_current_win(previous_wind)
end
end
local function extend(t1, t2)
for k, v in pairs(t2) do
t1[k] = v
end
end
local function update_statusline()
vim.cmd('redrawstatus!')
end
local function on_stdout(id, data, event)
if id ~= runner_jobid then
return
end
if vim.api.nvim_buf_is_valid(code_runner_bufnr) then
vim.api.nvim_buf_set_option(code_runner_bufnr, 'modifiable', true)
vim.api.nvim_buf_set_lines(code_runner_bufnr, runner_lines, runner_lines + 1, false, data)
vim.api.nvim_buf_set_option(code_runner_bufnr, 'modifiable', false)
runner_lines = runner_lines + #data
if winid >= 0 then
vim.api.nvim_win_set_cursor(winid, { vim.api.nvim_buf_line_count(code_runner_bufnr), 1 })
end
update_statusline()
end
end
local function on_stderr(id, data, event)
if id ~= runner_jobid then
return
end
runner_status.has_errors = true
if vim.api.nvim_buf_is_valid(code_runner_bufnr) then
vim.api.nvim_buf_set_option(code_runner_bufnr, 'modifiable', true)
vim.api.nvim_buf_set_lines(code_runner_bufnr, runner_lines, runner_lines + 1, false, data)
vim.api.nvim_buf_set_option(code_runner_bufnr, 'modifiable', false)
runner_lines = runner_lines + #data
if winid >= 0 then
vim.api.nvim_win_set_cursor(winid, { vim.api.nvim_buf_line_count(code_runner_bufnr), 1 })
end
update_statusline()
end
end
local function on_exit(id, code, single)
if id ~= runner_jobid then
return
end
end_time = vim.fn.reltime(start_time)
runner_status.is_running = false
runner_status.exit_single = single
runner_status.exit_code = code
local done = {
'',
'[Done] exited with code=' .. code .. ', single=' .. single .. ' in ' .. str.trim(
vim.fn.reltimestr(end_time)
) .. ' seconds',
}
if vim.api.nvim_buf_is_valid(code_runner_bufnr) then
vim.api.nvim_buf_set_option(code_runner_bufnr, 'modifiable', true)
vim.api.nvim_buf_set_lines(code_runner_bufnr, runner_lines, runner_lines + 1, false, done)
vim.api.nvim_buf_set_option(code_runner_bufnr, 'modifiable', false)
if winid >= 0 then
vim.api.nvim_win_set_cursor(winid, { vim.api.nvim_buf_line_count(code_runner_bufnr), 1 })
end
update_statusline()
end
end
local function merge_list(...)
local t = {}
for _, tb in ipairs({ ... }) do
for _, v in ipairs(tb) do
table.insert(t, v)
end
end
return t
end
local function on_compile_exit(id, code, single)
if id ~= runner_jobid then
return
end
if code == 0 and single == 0 then
runner_jobid = job.start(target, {
on_stdout = on_stdout,
on_stderr = on_stderr,
on_exit = on_exit,
})
if runner_jobid > 0 then
runner_status = {
is_running = true,
has_errors = false,
exit_code = 0,
exit_single = 0,
}
end
else
end_time = vim.fn.reltime(start_time)
runner_status.is_running = false
runner_status.exit_code = code
runner_status.exit_single = single
local done = {
'',
'[Done] exited with code=' .. code .. ', single=' .. single .. ' in ' .. str.trim(
vim.fn.reltimestr(end_time)
) .. ' seconds',
}
if vim.api.nvim_buf_is_valid(code_runner_bufnr) then
vim.api.nvim_buf_set_option(code_runner_bufnr, 'modifiable', true)
vim.api.nvim_buf_set_lines(code_runner_bufnr, runner_lines, runner_lines + 1, false, done)
vim.api.nvim_buf_set_option(code_runner_bufnr, 'modifiable', false)
if winid >= 0 then
vim.api.nvim_win_set_cursor(winid, { vim.api.nvim_buf_line_count(code_runner_bufnr), 1 })
end
update_statusline()
end
end
end
local function async_run(runner, ...)
if type(runner) == 'string' then
local cmd = runner
pcall(function()
local f
if selected_file ~= '' then
f = selected_file
else
f = vim.fn.bufname('%')
end
cmd = vim.fn.printf(runner, f)
end)
logger.info(' cmd:' .. cmd)
vim.api.nvim_buf_set_option(code_runner_bufnr, 'modifiable', true)
vim.api.nvim_buf_set_lines(
code_runner_bufnr,
runner_lines,
-1,
false,
{ '[Running] ' .. cmd, '', vim.fn['repeat']('-', 20) }
)
vim.api.nvim_buf_set_option(code_runner_bufnr, 'modifiable', false)
runner_lines = runner_lines + 3
start_time = vim.fn.reltime()
local opts = select(1, ...) or {}
extend(opts, {
on_stdout = on_stdout,
on_stderr = on_stderr,
on_exit = on_exit,
})
runner_jobid = job.start(cmd, opts)
elseif type(runner) == 'table' and #runner == 2 then
target = file.unify_path(vim.fn.tempname(), ':p')
local dir = vim.fn.fnamemodify(target, ':h')
if vim.fn.isdirectory(dir) == 0 then
vim.fn.mkdir(dir, 'p')
end
local compile_cmd
local usestdin
local compile_cmd_info
if type(runner[1]) == 'table' then
local exe
if type(runner[1].exe) == 'function' then
exe = runner[1].exe()
elseif type(runner[1].exe) == 'string' then
exe = { runner[1].exe }
end
usestdin = runner[1].usestdin or false
compile_cmd = merge_list(exe, { runner[1].targetopt or '' }, { target }, runner[1].opt)
if not usestdin then
local f
if selected_file == '' then
f = vim.fn.bufname('%')
else
f = selected_file
end
compile_cmd = merge_list(compile_cmd, { f })
end
elseif type(runner[1]) == 'string' then
end
if type(compile_cmd) == 'table' then
if usestdin then
compile_cmd_info = vim.inspect(merge_list(compile_cmd, { 'STDIN' }))
else
compile_cmd_info = vim.inspect(compile_cmd)
end
else
if usestdin then
compile_cmd_info = compile_cmd .. ' STDIN'
else
compile_cmd_info = compile_cmd
end
end
vim.api.nvim_buf_set_option(code_runner_bufnr, 'modifiable', true)
vim.api.nvim_buf_set_lines(
code_runner_bufnr,
runner_lines,
-1,
false,
{ '[Compile] ' .. compile_cmd_info, '[Running] ' .. target, '', vim.fn['repeat']('-', 20) }
)
vim.api.nvim_buf_set_option(code_runner_bufnr, 'modifiable', false)
runner_lines = runner_lines + 4
start_time = vim.fn.reltime()
if
type(compile_cmd) == 'string'
or type(compile_cmd) == 'table' and vim.fn.executable(compile_cmd[1] or '') == 1
then
runner_jobid = job.start(compile_cmd, {
on_stdout = on_stdout,
on_stderr = on_stderr,
on_exit = on_compile_exit,
})
if usestdin and runner_jobid > 0 then
local range = runner[1].range or { 1, '$' }
job.send(runner_jobid, vim.fn.getline(unpack(range)))
job.chanclose(runner_jobid, 'stdin')
job.stop(runner_jobid)
end
else
local exe = compile_cmd[1] or ''
vim.api.nvim_buf_set_option(code_runner_bufnr, 'modifiable', true)
vim.api.nvim_buf_set_lines(
code_runner_bufnr,
runner_lines,
-1,
false,
{ exe .. ' is not executable, make sure ' .. exe .. ' is in your PATH' }
)
vim.api.nvim_buf_set_option(code_runner_bufnr, 'modifiable', false)
end
elseif type(runner) == 'table' then
local cmd = {}
if type(runner.exe) == 'function' then
cmd = merge_list(cmd, runner.exe())
elseif type(runner.exe) == 'string' then
cmd = { runner.exe }
end
local usestdin = runner.usestdin or false
cmd = merge_list(cmd, runner.opt)
if not usestdin then
if selected_file == '' then
cmd = merge_list(cmd, vim.fn.bufname('%'))
else
cmd = merge_list(cmd, selected_file)
end
end
logger.info(' cmd:' .. vim.inspect(cmd))
local running_command = table.concat(cmd, ' ')
if usestdin then
running_command = running_command .. ' STDIN'
end
vim.api.nvim_buf_set_option(code_runner_bufnr, 'modifiable', true)
vim.api.nvim_buf_set_lines(
code_runner_bufnr,
runner_lines,
-1,
false,
{ '[Running] ' .. running_command, '', vim.fn['repeat']('-', 20) }
)
vim.api.nvim_buf_set_option(code_runner_bufnr, 'modifiable', false)
runner_lines = runner_lines + 3
start_time = vim.fn.reltime()
if vim.fn.empty(cmd) == 0 and vim.fn.executable(cmd[1]) == 1 then
runner_jobid = job.start(cmd, {
on_stdout = on_stdout,
on_stderr = on_stderr,
on_exit = on_exit,
})
if usestdin and runner_jobid > 0 then
local range = runner.range or { 1, '$' }
-- if selected file is not empty
-- read the context from selected file.
local text
if selected_file == '' then
text = vim.fn.getline(unpack(range))
else
text = vim.fn.readfile(selected_file, '')
end
job.send(runner_jobid, text)
job.chanclose(runner_jobid, 'stdin')
job.stop(runner_jobid)
end
else
local exe = cmd[1] or ''
vim.api.nvim_buf_set_option(code_runner_bufnr, 'modifiable', true)
vim.api.nvim_buf_set_lines(
code_runner_bufnr,
runner_lines,
-1,
false,
{ exe .. ' is not executable, make sure ' .. exe .. ' is in your PATH' }
)
vim.api.nvim_buf_set_option(code_runner_bufnr, 'modifiable', false)
end
end
if runner_jobid > 0 then
runner_status = {
is_running = true,
has_errors = false,
exit_code = 0,
exit_single = 0,
}
end
end
function M.open(...)
stop_runner()
runner_jobid = 0
runner_lines = 0
runner_status = {
is_running = false,
has_errors = false,
exit_code = 0,
exit_single = 0,
}
local language = vim.o.filetype
local runner = select(1, ...) or runners[language] or ''
local opts = select(2, ...) or {}
logger.debug('runner is:\n' .. vim.inspect(runner))
logger.debug('opt is:\n' .. vim.inspect(opts))
if vim.fn.empty(runner) == 0 then
open_win()
async_run(runner, opts)
update_statusline()
else
end
end
function M.reg_runner(ft, runner)
runners[ft] = runner
end
function M.status()
local running_nr = 0
local running_done = 0
for _, v in ipairs(task_status) do
if v.is_running then
running_nr = running_nr + 1
else
running_done = running_done + 1
end
end
if runner_status.is_running then
running_nr = running_nr + 1
end
return string.format(' %s running, %s done', running_nr, running_done)
end
function M.close()
close_win()
end
function M.select_file()
runner_lines = 0
runner_status = {
is_running = false,
has_errors = false,
exit_code = 0,
exit_single = 0
}
if vim.loop.os_uname().sysname == 'Windows_NT' then
-- what the fuck, why need trim?
-- because powershell comamnd output has `\n` at the end, and filetype detection failed.
selected_file = str.trim(vim.fn.system({'powershell', "Add-Type -AssemblyName System.windows.forms|Out-Null;$f=New-Object System.Windows.Forms.OpenFileDialog;$f.Filter='Model Files All files (*.*)|*.*';$f.showHelp=$true;$f.ShowDialog()|Out-Null;$f.FileName"}))
end
if selected_file == '' then
logger.debug('file to get selected filename!')
return
else
logger.debug('selected file is:' .. selected_file)
local ft = vim.filetype.match({filename = selected_file})
if not ft then
logger.debug('failed to detect filetype of selected file:' .. selected_file)
return
end
local runner = runners[ft]
logger.info(vim.inspect(runner))
if runner then
open_win()
async_run(runner)
update_statusline()
end
end
end
function M.select_language()
end
return M

View File

@ -7,17 +7,23 @@ syn match KeyBindings /\[Running\]/
syn match KeyBindings /\[Compile\]/
syn match RunnerCmd /\(\[Running\]\ \)\@<=.*/
syn match RunnerCmd /\(\[Compile\]\ \)\@<=.*/
syn match DoneSucceeded /\[Done]\(\ exited\ with\ code=0\)\@=/
syn match DoneSucceeded /\[Done]\(\ exited\ with\ code=0, single=0\)\@=/
syn match DoneSucceeded /\[Done]\(\ exited\ with\ code=0 in\)\@=/
syn match DoneFailed /\[Done]\(\ exited\ with\ code=[^0]\)\@=/
syn match DoneFailed /\[Done]\(\ exited\ with\ code=0, single=[^0]\)\@=/
syn match ExitCode /\(\[Done\]\ exited\ with \)\@<=code=0/
syn match ExitCodeFailed /\(\[Done\]\ exited\ with \)\@<=code=[^0]/
syn match ExitCodeFailed /\(\[Done\]\ exited\ with \)\@<=code=[1-9]\d*/
syn match SingleCode /single=0/
syn match SingleCodeFailed /single=[^0]/
hi def link RunnerCmd Comment
hi def link KeyBindings String
hi def link DoneSucceeded String
hi def link DoneFailed WarningMsg
hi def link ExitCode MoreMsg
hi def link SingleCode MoreMsg
hi def link ExitCodeFailed WarningMsg
hi def link SingleCodeFailed WarningMsg
let s:shellcmd_colors =
\ [
\ '#6c6c6c', '#ff6666', '#66ff66', '#ffd30a',

View File

@ -1,6 +1,6 @@
local job = require('spacevim.api.job')
local jobid = job.start('echo 1', {
local jobid = job.start({'lua53', '-'}, {
on_stdout = function(id, data, event)
vim.print(id)
vim.print(vim.inspect(data))
@ -19,5 +19,9 @@ local jobid = job.start('echo 1', {
})
-- job.send(jobid, 'hello world')
-- job.stop(jobid)
job.send(jobid, 'print(1)\n')
job.send(jobid, 'print(1)\n')
job.send(jobid, 'print(1)\n')
job.send(jobid, 'print(1)\n')
job.chanclose(jobid, 'stdin')
job.stop(jobid)