diff --git a/lua/custom/plugins/term-helper.lua b/lua/custom/plugins/term-helper.lua new file mode 100644 index 0000000..c09ace6 --- /dev/null +++ b/lua/custom/plugins/term-helper.lua @@ -0,0 +1,141 @@ +-- Terminal helper +-- from: https://github.com/jmbuhr/nvim-config +-- + +---@module 'utils.term' +local M = { + ---Map of buffers to a terminals + ---@type table + buf_term_map = {}, +} + +---A terminal has a buffer number, a type and a channel id +---@class Terminal +---@field bufnr integer +---@field type string? +---@field channel_id integer + +---Get the channel id of a terminal buffer +---@param bufnr integer +---@return integer|nil +local function get_channel_id(bufnr) + return vim.api.nvim_get_option_value('channel', { buf = bufnr }) +end + +---Get the type of the terminal buffer +---like ipython, R, bash, etc. +---based on the buffer name +---@param buf integer +---@return string|nil +local function get_term_type(buf) + local name = vim.api.nvim_buf_get_name(buf) + local term_type = name:match 'term://.*/%d+:(.*)' + if not term_type then + return nil + end + -- drop flags like --no-confirm-exit + term_type = term_type:match '([^%s]+)' + -- drop the path to the executable + term_type = term_type:match '([^/]+)$' + return term_type +end + +---Find nvim terminal buffers +local function list_terminals() + local terms = {} + for _, buf in ipairs(vim.api.nvim_list_bufs()) do + if vim.api.nvim_buf_is_valid(buf) and vim.api.nvim_get_option_value('buftype', { buf = buf }) == 'terminal' then + local chan_id = get_channel_id(buf) + if not chan_id then + goto continue + end + ---@type Terminal + local term = { + bufnr = buf, + type = get_term_type(buf), + channel_id = chan_id, + } + table.insert(terms, term) + end + ::continue:: + end + return terms +end + +---For ipython sending +local cpaste_start = '%cpaste -q\n' +local cpaste_end = '--\n' +local cpaste_pause = 10 + +---Send lines to a terminal +---@param lines string[] +---@param term Terminal +---@return nil +local send_lines = function(lines, term) + local chan_id = term.channel_id + local text = table.concat(lines, '\n') .. '\n' + if term.type == 'ipython' then + vim.fn.chansend(chan_id, cpaste_start) + vim.uv.sleep(cpaste_pause) + end + vim.fn.chansend(chan_id, text) + if term.type == 'ipython' then + vim.fn.chansend(chan_id, cpaste_end) + end +end + +local s = [[ +print("hello world!") +def hello(): + print("hello") +hello() +]] + +local ls = { s } + +---Connect current buffer to a terminal +local connect = function() + local bufnr = vim.api.nvim_get_current_buf() + local terms = list_terminals() + vim.ui.select(terms, { + prompt = 'Select terminal', + format_item = function(item) + return item.type .. ' ' .. item.bufnr + end, + }, function(choice) + if choice == nil then + return + end + M.buf_term_map[bufnr] = choice + end) +end + +M.send = function(lines) + local bufnr = vim.api.nvim_get_current_buf() + local term = M.buf_term_map[bufnr] + if not term then + connect() + return + end + send_lines(lines, term) + -- scroll to the bottom + local term_win = vim.fn.bufwinid(term.bufnr) + local n = vim.api.nvim_buf_line_count(term.bufnr) + vim.api.nvim_win_set_cursor(term_win, { n, 0 }) +end + +M.send_visual = function() + -- get the selected text + vim.cmd.normal { '"zy', bang = true } + local selection = vim.fn.getreg 'z' + M.send { selection } +end + +vim.keymap.set('v', '', function() + M.send_visual() +end, { noremap = true, silent = true }) + +-- connect +vim.keymap.set('n', 'ct', function() + connect() +end, { noremap = true, silent = true }) diff --git a/lua/kickstart/core/autocommands.lua b/lua/kickstart/core/autocommands.lua index 7fef745..f86c3d1 100644 --- a/lua/kickstart/core/autocommands.lua +++ b/lua/kickstart/core/autocommands.lua @@ -12,5 +12,27 @@ vim.api.nvim_create_autocmd('TextYankPost', { end, }) +-- Use with Quarto + vim-slime +-- From: https://github.com/jmbuhr/nvim-config +-- +local function set_terminal_keymaps() + local opts = { buffer = 0 } + vim.keymap.set('t', '', [[]], opts) + vim.keymap.set('t', '', [[wincmd h]], opts) + vim.keymap.set('t', '', [[wincmd j]], opts) + vim.keymap.set('t', '', [[wincmd k]], opts) + vim.keymap.set('t', '', [[wincmd l]], opts) +end + +vim.api.nvim_create_autocmd({ 'TermOpen' }, { + pattern = { '*' }, + callback = function(_) + vim.cmd.setlocal 'nonumber' + vim.wo.signcolumn = 'no' + set_terminal_keymaps() + end, +}) + -- The line beneath this is called `modeline`. See `:help modeline` --- vim: ts=2 sts=2 sw=2 et \ No newline at end of file +-- vim: ts=2 sts=2 sw=2 et + diff --git a/lua/kickstart/plugins/quarto.lua b/lua/kickstart/plugins/quarto.lua index 867a14f..602c747 100644 --- a/lua/kickstart/plugins/quarto.lua +++ b/lua/kickstart/plugins/quarto.lua @@ -7,18 +7,110 @@ return { { 'quarto-dev/quarto-nvim', + dev = false, dependencies = { 'jmbuhr/otter.nvim', 'nvim-treesitter/nvim-treesitter', + 'jpalardy/vim-slime', -- optional + }, + opts = { + lspFeatures = { + enabled = true, + chunks = 'curly', + }, + codeRunner = { + enabled = true, + default_method = 'slime', + }, }, }, - -- { - -- 'jpalardy/vim-slime', - -- init = function() - -- vim.g.slime_target = 'kitty' - -- vim.g.slime_no_mappings = 1 - -- end, - -- }, + { -- send code from python/r/qmd documets to a terminal or REPL + -- like ipython, R, bash + 'jpalardy/vim-slime', + dev = false, + init = function() + vim.b['quarto_is_python_chunk'] = false + Quarto_is_in_python_chunk = function() + require('otter.tools.functions').is_otter_language_context 'python' + end + + vim.cmd [[ + let g:slime_dispatch_ipython_pause = 100 + function SlimeOverride_EscapeText_quarto(text) + call v:lua.Quarto_is_in_python_chunk() + if exists('g:slime_python_ipython') && len(split(a:text,"\n")) > 1 && b:quarto_is_python_chunk && !(exists('b:quarto_is_r_mode') && b:quarto_is_r_mode) + return ["%cpaste -q\n", g:slime_dispatch_ipython_pause, a:text, "--", "\n"] + else + if exists('b:quarto_is_r_mode') && b:quarto_is_r_mode && b:quarto_is_python_chunk + return [a:text, "\n"] + else + return [a:text] + end + end + endfunction + ]] + + vim.g.slime_target = 'neovim' + vim.g.slime_no_mappings = true + vim.g.slime_python_ipython = 1 + end, + config = function() + vim.g.slime_input_pid = false + vim.g.slime_suggest_default = true + vim.g.slime_menu_config = false + vim.g.slime_neovim_ignore_unlisted = true + + local function mark_terminal() + local job_id = vim.b.terminal_job_id + vim.print('job_id: ' .. job_id) + end + + local function set_terminal() + vim.fn.call('slime#config', {}) + end + vim.keymap.set('n', 'cm', mark_terminal, { desc = '[m]ark terminal' }) + vim.keymap.set('n', 'cs', set_terminal, { desc = '[s]et terminal' }) + end, + opts = { + debug = true, + }, + }, + { + 'benlubas/molten-nvim', + dev = false, + enabled = true, + version = '^1.0.0', -- use version <2.0.0 to avoid breaking changes + build = ':UpdateRemotePlugins', + init = function() + vim.g.molten_image_provider = 'image.nvim' + -- vim.g.molten_output_win_max_height = 20 + vim.g.molten_auto_open_output = true + vim.g.molten_auto_open_html_in_browser = true + vim.g.molten_tick_rate = 200 + end, + config = function() + local init = function() + local quarto_cfg = require('quarto.config').config + quarto_cfg.codeRunner.default_method = 'molten' + vim.cmd [[MoltenInit]] + end + local deinit = function() + local quarto_cfg = require('quarto.config').config + quarto_cfg.codeRunner.default_method = 'slime' + vim.cmd [[MoltenDeinit]] + end + vim.keymap.set('n', 'mi', init, { silent = true, desc = 'Initialize molten' }) + vim.keymap.set('n', 'md', deinit, { silent = true, desc = 'Stop molten' }) + vim.keymap.set('n', 'mp', ':MoltenImagePopup', { silent = true, desc = 'molten image popup' }) + vim.keymap.set('n', 'mb', ':MoltenOpenInBrowser', { silent = true, desc = 'molten open in browser' }) + vim.keymap.set('n', 'mh', ':MoltenHideOutput', { silent = true, desc = 'hide output' }) + vim.keymap.set('n', 'ms', ':noautocmd MoltenEnterOutput', { silent = true, desc = 'show/enter output' }) + end, + opts = { + auto_scroll = true, + }, + }, + -- { -- 'benlubas/molten-nvim', -- version = '^1.0.0', -- use version <2.0.0 to avoid breaking changes @@ -29,49 +121,52 @@ return { -- vim.g.molten_image_provider = 'image.nvim' -- vim.g.molten_output_win_max_height = 20 -- end, - -- }, - -- { - -- '3rd/image.nvim', - -- build = false, -- so that it doesn't build the rock https://github.com/3rd/image.nvim/issues/91#issuecomment-2453430239 -- opts = { - -- backend = 'kitty', - -- processor = 'magick_cli', -- or "magick_rock" - -- integrations = { - -- markdown = { - -- enabled = true, - -- clear_in_insert_mode = false, - -- download_remote_images = true, - -- only_render_image_at_cursor = false, - -- only_render_image_at_cursor_mode = 'popup', - -- floating_windows = false, -- if true, images will be rendered in floating markdown windows - -- filetypes = { 'markdown', 'vimwiki', 'quarto' }, -- markdown extensions (ie. quarto) can go here - -- }, - -- neorg = { - -- enabled = true, - -- filetypes = { 'norg' }, - -- }, - -- typst = { - -- enabled = true, - -- filetypes = { 'typst' }, - -- }, - -- html = { - -- enabled = false, - -- }, - -- css = { - -- enabled = false, - -- }, - -- }, - -- max_width = nil, - -- max_height = nil, - -- max_width_window_percentage = nil, - -- max_height_window_percentage = 50, - -- window_overlap_clear_enabled = false, -- toggles images when windows are overlapped - -- window_overlap_clear_ft_ignore = { 'cmp_menu', 'cmp_docs', 'snacks_notif', 'scrollview', 'scrollview_sign' }, - -- editor_only_render_when_focused = false, -- auto show/hide images when the editor gains/looses focus - -- tmux_show_only_in_active_window = false, -- auto show/hide images in the correct Tmux window (needs visual-activity off) - -- hijack_file_patterns = { '*.png', '*.jpg', '*.jpeg', '*.gif', '*.webp', '*.avif' }, -- render image files as images when opened + -- debug = true, -- }, -- }, + { + '3rd/image.nvim', + build = false, -- so that it doesn't build the rock https://github.com/3rd/image.nvim/issues/91#issuecomment-2453430239 + opts = { + backend = 'kitty', + processor = 'magick_cli', -- or "magick_rock" + integrations = { + markdown = { + enabled = true, + clear_in_insert_mode = false, + download_remote_images = true, + only_render_image_at_cursor = false, + only_render_image_at_cursor_mode = 'popup', + floating_windows = false, -- if true, images will be rendered in floating markdown windows + filetypes = { 'markdown', 'vimwiki', 'quarto' }, -- markdown extensions (ie. quarto) can go here + }, + neorg = { + enabled = true, + filetypes = { 'norg' }, + }, + typst = { + enabled = true, + filetypes = { 'typst' }, + }, + html = { + enabled = false, + }, + css = { + enabled = false, + }, + }, + max_width = nil, + max_height = nil, + max_width_window_percentage = nil, + max_height_window_percentage = 50, + window_overlap_clear_enabled = false, -- toggles images when windows are overlapped + window_overlap_clear_ft_ignore = { 'cmp_menu', 'cmp_docs', 'snacks_notif', 'scrollview', 'scrollview_sign' }, + editor_only_render_when_focused = false, -- auto show/hide images when the editor gains/looses focus + tmux_show_only_in_active_window = false, -- auto show/hide images in the correct Tmux window (needs visual-activity off) + hijack_file_patterns = { '*.png', '*.jpg', '*.jpeg', '*.gif', '*.webp', '*.avif' }, -- render image files as images when opened + }, + }, -- { -- 'HakonHarnes/img-clip.nvim', -- event = 'BufEnter',