diff --git a/autoload/SpaceVim/plugins/repl.vim b/autoload/SpaceVim/plugins/repl.vim index 58e9f0094..c2fd197da 100644 --- a/autoload/SpaceVim/plugins/repl.vim +++ b/autoload/SpaceVim/plugins/repl.vim @@ -6,6 +6,38 @@ " License: GPLv3 "============================================================================= +if has('nvim-0.9.0') + function! SpaceVim#plugins#repl#send(type, ...) abort + if a:type == 'raw' + lua require('spacevim.plugin.repl').send( + \ require('spacevim').eval('a:type'), + \ require('spacevim').eval("get(a:000, 0, '')") + \ ) + else + lua require('spacevim.plugin.repl').send( + \ require('spacevim').eval('a:type') + \ ) + endif + endfunction + function! SpaceVim#plugins#repl#start(ft) abort + lua require("spacevim.plugin.repl").start( + \ require("spacevim").eval("a:ft") + \ ) + endfunction + function! SpaceVim#plugins#repl#status() abort + return luaeval('require("spacevim.plugin.repl").status()') + endfunction + function! SpaceVim#plugins#repl#reg(ft, execute) abort + lua require("spacevim.plugin.repl").reg( + \ require("spacevim").eval("a:ft"), + \ require("spacevim").eval("a:execute") + \ ) + endfunction + finish +endif + + + "" " @section repl, usage-repl " @parentsection usage @@ -97,10 +129,6 @@ function! s:start(exe) abort call s:BUFFER.buf_set_lines(s:bufnr, s:lines , s:lines + 3, 0, ['[REPL executable] ' . string(a:exe), '', repeat('-', 20)]) call s:WINDOW.set_cursor(s:winid, [s:BUFFER.line_count(s:bufnr), 0]) let s:lines += 3 - let s:_out_data = [''] - let s:_current_line = '' - " this only for has('nvim') && exists('*chanclose') - let s:_out_data = [''] let s:job_id = s:JOB.start(a:exe,{ \ 'on_stdout' : function('s:on_stdout'), \ 'on_stderr' : function('s:on_stderr'), @@ -123,7 +151,7 @@ function! s:on_stdout(job_id, data, event) abort let s:lines += len(a:data) if s:WINDOW.get_cursor(s:winid)[0] == s:BUFFER.line_count(s:bufnr) - len(a:data) call s:WINDOW.set_cursor(s:winid, [s:BUFFER.line_count(s:bufnr), 0]) - endi + endif call s:update_statusline() endif endfunction diff --git a/lua/spacevim/api/job.lua b/lua/spacevim/api/job.lua index 325b37543..83c6afc90 100644 --- a/lua/spacevim/api/job.lua +++ b/lua/spacevim/api/job.lua @@ -226,6 +226,7 @@ function M.send(id, data) -- {{{ end elseif type(data) == 'string' then stdin:write(data) + stdin:write('\n') elseif data == nil then stdin:write('', function() stdin:shutdown(function() diff --git a/lua/spacevim/api/vim/option.lua b/lua/spacevim/api/vim/option.lua new file mode 100644 index 000000000..08ce2a4e2 --- /dev/null +++ b/lua/spacevim/api/vim/option.lua @@ -0,0 +1,18 @@ +local M = {} + +function M.setlocalopt(buf, win, opts) + for o, value in pairs(opts) do + local info = vim.api.nvim_get_option_info2(o, {}) + if info.scope == 'win' then + vim.api.nvim_set_option_value(o, value, { + win = win, + }) + elseif info.scope == 'buf' then + vim.api.nvim_set_option_value(o, value, { + buf = buf, + }) + end + end +end + +return M diff --git a/lua/spacevim/plugin/repl.lua b/lua/spacevim/plugin/repl.lua new file mode 100644 index 000000000..a0ed84341 --- /dev/null +++ b/lua/spacevim/plugin/repl.lua @@ -0,0 +1,212 @@ +--============================================================================= +-- repl.lua --- REPL for spacevim +-- Copyright (c) 2016-2022 Wang Shidong & Contributors +-- Author: Wang Shidong < wsdjeg@outlook.com > +-- URL: https://spacevim.org +-- License: GPLv3 +--============================================================================= + +local job = require('spacevim.api.job') +local nt = require('spacevim.api.notify') +local vopt = require('spacevim.api.vim.option') +local str = require('spacevim.api.data.string') + +local log = require('spacevim.logger').derive('repl') + +local lines = 0 +local bufnr = -1 +local winid = -1 +local status = {} +local start_time +local end_time +local job_id = 0 +local exes = {} + +local M = {} + +local function close() + if job_id > 0 then + job.stop(job_id) + job_id = 0 + end + if vim.api.nvim_buf_is_valid(bufnr) then + vim.cmd('bd ' .. bufnr) + end +end + +local function insert() + vim.fn.inputsave() + local input = vim.fn.input('input >') + if vim.fn.empty(input) == 0 then + if job_id == 0 then + nt.notify('please restart the REPL', 'WarningMsg') + else + job.send(job_id, input) + end + end + vim.api.nvim_echo({}, false, {}) + vim.fn.inputrestore() +end + +local function close_repl() + if job_id > 0 then + job.stop(job_id) + job_id = 0 + end +end + +local function open_windows() + if vim.api.nvim_buf_is_valid(bufnr) then + vim.cmd('bd ' .. bufnr) + end + local previous_win = vim.api.nvim_get_current_win() + vim.cmd('botright split __REPL__') + bufnr = vim.api.nvim_get_current_buf() + winid = vim.api.nvim_get_current_win() + local l = math.floor(vim.o.lines * 30 / 100) + vim.cmd('resize ' .. l) + vim.api.nvim_set_current_win(previous_win) + vopt.setlocalopt(bufnr, winid, { + buftype = 'nofile', + bufhidden = 'wipe', + buflisted = false, + list = false, + swapfile = false, + wrap = false, + cursorline = true, + spell = false, + number = false, + relativenumber = false, + winfixheight = true, + modifiable = false, + filetype = 'SpaceVimREPL', + }) + vim.api.nvim_buf_set_keymap(bufnr, 'n', 'q', '', { + callback = close, + }) + vim.api.nvim_buf_set_keymap(bufnr, 'n', 'i', '', { + callback = insert, + }) + local id = vim.api.nvim_create_augroup('spacevim_repl', { + clear = true, + }) + vim.api.nvim_create_autocmd({ 'BufWipeout' }, { + group = id, + buffer = bufnr, + callback = close_repl, + }) +end + +local function on_stdout(_, data) + if vim.api.nvim_buf_is_valid(bufnr) then + vim.api.nvim_buf_set_option(bufnr, 'modifiable', true) + vim.api.nvim_buf_set_lines(bufnr, lines, lines + 1, false, data) + vim.api.nvim_buf_set_option(bufnr, 'modifiable', false) + lines = lines + #data + local cursor = vim.api.nvim_win_get_cursor(winid) + if cursor[1] == vim.api.nvim_buf_line_count(bufnr) - #data then + vim.api.nvim_win_set_cursor(winid, { vim.api.nvim_buf_line_count(bufnr), 0 }) + end + end +end + +local function on_stderr(_, data) + status.has_errors = true + on_stdout(_, data) +end + +local function on_exit(id, code, single) + end_time = vim.fn.reltime(start_time) + status.is_exit = true + status.exit_code = code + local done = {'', '[Done] exited with code=' .. code .. ' in ' .. str.trim(vim.fn.reltimestr(end_time)) .. ' seconds'} + if vim.api.nvim_buf_is_valid(bufnr) then + vim.api.nvim_buf_set_option(bufnr, 'modifiable', true) + vim.api.nvim_buf_set_lines(bufnr, lines, lines + 1, false, done) + vim.api.nvim_buf_set_option(bufnr, 'modifiable', false) + end + job_id = 0 +end + +local function start(exe) + lines = 0 + status = { + is_running = true, + is_exit = false, + has_errors = false, + exit_code = 0, + } + + start_time = vim.fn.reltime() + open_windows() + vim.api.nvim_buf_set_option(bufnr, 'modifiable', true) + vim.api.nvim_buf_set_lines( + bufnr, + lines, + lines + 3, + false, + { '[REPL executable] ' .. vim.fn.string(exe), '', string.rep('-', 20) } + ) + vim.api.nvim_buf_set_option(bufnr, 'modifiable', false) + vim.api.nvim_win_set_cursor(winid, { vim.api.nvim_buf_line_count(bufnr), 0 }) + lines = lines + 3 + job_id = job.start(exe, { + on_stdout = on_stdout, + on_stderr = on_stderr, + on_exit = on_exit, + }) +end + +function M.start(ft) + log.info('start repl for filetype:' .. ft) + local exe = exes[ft] or '' + log.debug('get the command:' .. vim.inspect(exe)) + if exe ~= '' then + start(exe) + else + vim.api.nvim_echo({ { 'no REPL executable for ' .. ft, 'WarningMsg' } }, false, {}) + end +end + +function M.send(t, ...) + if job_id == 0 then + nt.notify('please restart the REPL', 'WarningMsg') + else + if t == 'line' then + job.send(job_id, { vim.api.nvim_get_current_line(), '' }) + elseif t == 'buffer' then + local data = vim.fn.getline(1, '$') + table.insert(data, '') + job.send(job_id, data) + elseif t == 'raw' then + local context = select(1, ...) + if type(context) == "string" then + job.send(job_id, context) + end + elseif t == 'selection' then + local b = vim.fn.getpos("'<") + local e = vim.fn.getpos("'>") + if b[2] ~= 0 and e[2] ~= 0 then + local data = vim.fn.getline(b[2], e[2]) + table.insert(data, '') + job.send(job_id, data) + else + nt.notify('no selection text', 'WarningMsg') + end + end + end +end + +function M.reg(ft, execute) + exes[ft] = execute +end + +function M.status() + if status.is_running then + return 'running' + elseif status.is_exit then + return 'exit code:' .. status.exit_code .. ' time:' .. str.trim(vim.fn.reltimestr(end_time)) + end +end + +return M