Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions lua/plugins/config/image.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
local M = {}

function M.setup()
local image = require 'image'
image:setup {
backend = 'kitty',
processor = 'magick_cli',
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,
filetypes = { 'markdown', 'vimwiki' },
},
neorg = {
enabled = true,
filetypes = { 'norg' },
},
typst = {
enabled = true,
filetypes = { 'typst' },
},
html = {
enabled = false,
},
css = {
enabled = false,
},
},
max_width = 100,
max_height = 12,
max_width_window_percentage = math.huge,
max_height_window_percentage = math.huge,
scale_factor = 1.0,
window_overlap_clear_enabled = true,
-- window_overlap_clear_ft_ignore = { 'cmp_menu', 'cmp_docs', 'snacks_notif', 'scrollview', 'scrollview_sign' },
window_overlap_clear_ft_ignore = { 'cmp_menu', 'cmp_docs', '' },
editor_only_render_when_focused = false,
tmux_show_only_in_active_window = false,
hijack_file_patterns = { '*.png', '*.jpg', '*.jpeg', '*.gif', '*.webp', '*.avif' },
}
end

return M
145 changes: 145 additions & 0 deletions lua/plugins/config/jupytext.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
-- Jupytext configuration
local M = {}

function M.setup()
-- Basic settings
vim.g.jupytext_fmt = 'py:percent'
vim.g.jupytext_command = 'jupytext'

-- Create user commands
local function create_commands()
vim.api.nvim_create_user_command('JupytextSync', function()
vim.cmd('!jupytext --sync %')
vim.notify("Notebook synced", vim.log.levels.INFO)
end, { desc = "Sync current file with its notebook pair" })

vim.api.nvim_create_user_command('JupytextPair', function()
vim.cmd('!jupytext --set-formats ipynb,py:percent %')
vim.notify("Notebook paired with .py file", vim.log.levels.INFO)
end, { desc = "Pair notebook with Python file" })

vim.api.nvim_create_user_command('JupytextConvert', function(opts)
local format = opts.args ~= "" and opts.args or "py:percent"
vim.cmd('!jupytext --to ' .. format .. ' %')
vim.notify("Converted to " .. format, vim.log.levels.INFO)
end, {
nargs = '?',
desc = "Convert notebook to format (default: py:percent)",
complete = function()
return { 'py:percent', 'md', 'py:light', 'py:sphinx', 'jl:percent', 'r:percent' }
end
})
end

-- Setup auto-sync for paired files
local function setup_autosync()
local augroup = vim.api.nvim_create_augroup("JupytextSync", { clear = true })

vim.api.nvim_create_autocmd("BufWritePost", {
group = augroup,
pattern = { "*.py", "*.ipynb", "*.md", "*.jl", "*.r" },
callback = function()
local file = vim.fn.expand('%:p')
local bufnr = vim.api.nvim_get_current_buf()

-- Check if file has jupytext pairing metadata
local first_lines = vim.api.nvim_buf_get_lines(bufnr, 0, 15, false)
local has_pairing = false
local saw_jupytext = false

for _, line in ipairs(first_lines) do
if line:match("jupytext") then
saw_jupytext = true
end
if saw_jupytext and line:match("formats:") then
has_pairing = true
break
end
end

if has_pairing then
-- Run sync in background
vim.fn.jobstart({'jupytext', '--sync', file}, {
on_exit = function(_, exit_code)
if exit_code == 0 then
vim.schedule(function()
vim.notify("Jupytext: Synced with notebook", vim.log.levels.INFO)
end)
end
end
})
end
end,
desc = "Auto-sync Jupytext paired files"
})
end

-- Setup keymaps
local function setup_keymaps()
-- Only set these in relevant filetypes
vim.api.nvim_create_autocmd("FileType", {
pattern = { "python", "markdown", "julia", "r" },
callback = function()
local opts = { buffer = true, silent = true }
vim.keymap.set("n", "<leader>js", ":JupytextSync<CR>",
vim.tbl_extend("force", opts, { desc = "Sync with notebook" }))
vim.keymap.set("n", "<leader>jp", ":JupytextPair<CR>",
vim.tbl_extend("force", opts, { desc = "Pair with notebook" }))
vim.keymap.set("n", "<leader>jc", ":JupytextConvert ",
vim.tbl_extend("force", opts, { desc = "Convert to format" }))
end
})
end

-- Integration with Molten
local function setup_molten_integration()
-- When opening .ipynb files, offer to convert
vim.api.nvim_create_autocmd({"BufRead", "BufNewFile"}, {
pattern = "*.ipynb",
callback = function()
local file = vim.fn.expand('%:p')
local py_file = file:gsub('%.ipynb$', '.py')

-- Check if .py version exists
if vim.fn.filereadable(py_file) == 1 then
-- Close the current buffer and open the .py version
vim.cmd('buffer #') -- Go to alternate buffer
vim.cmd('bdelete! #') -- Delete the ipynb buffer
vim.cmd('edit ' .. py_file)
vim.notify("Opened paired .py file instead of .ipynb", vim.log.levels.INFO)
else
-- Offer to convert
vim.schedule(function()
vim.ui.select(
{ "Convert to .py", "Keep as JSON" },
{ prompt = "Convert notebook to Python?" },
function(choice)
if choice == "Convert to .py" then
local result = vim.fn.system('jupytext --to py:percent "' .. file .. '"')
if vim.v.shell_error == 0 then
vim.cmd('edit! ' .. py_file)
vim.notify("Converted and opened as .py", vim.log.levels.INFO)
else
vim.notify("Failed to convert: " .. result, vim.log.levels.ERROR)
end
end
end
)
end)
end
end,
desc = "Handle .ipynb files with Jupytext"
})
end

-- Initialize all components
create_commands()
setup_autosync()
setup_keymaps()
setup_molten_integration()
end

-- Run setup
M.setup()

return M
100 changes: 88 additions & 12 deletions lua/plugins/config/molten.lua
Original file line number Diff line number Diff line change
@@ -1,18 +1,94 @@
-- Molten (Jupyter) configuration
local M = {}

-- Keybindings
local function setup_keymaps()
local keymap = vim.keymap.set

-- Basic operations
keymap('n', '<leader>mi', ':MoltenInit<CR>', { desc = 'Molten Init', silent = true })
keymap('n', '<leader>ml', ':MoltenEvaluateLine<CR>', { desc = 'Evaluate Line', silent = true })
keymap('n', '<leader>mr', ':MoltenReevaluateCell<CR>', { desc = 'Re-evaluate Cell', silent = true })
keymap('v', '<leader>mv', ':<C-u>MoltenEvaluateVisual<CR>gv', { desc = 'Evaluate Visual', silent = true })

-- Additional useful keymaps
keymap('n', '<leader>md', ':MoltenDelete<CR>', { desc = 'Delete Cell', silent = true })
keymap('n', '<leader>mh', ':MoltenHideOutput<CR>', { desc = 'Hide Output', silent = true })
keymap('n', '<leader>mo', ':noautocmd MoltenEnterOutput<CR>', { desc = 'Enter Output', silent = true })

-- Better cell execution that always works
keymap('n', '<leader>mc', function()
-- Always use the operator approach for consistency
-- This finds cell boundaries based on # %% markers
vim.cmd 'MoltenEvaluateOperator'

-- Send 'ip' (inner paragraph) which respects the cell markers
local keys = vim.api.nvim_replace_termcodes('ip', true, false, true)
vim.api.nvim_feedkeys(keys, 'n', false)
end, { desc = 'Run Cell', silent = true })

-- Alternative: Run and move to next cell
keymap('n', '<leader>mn', function()
vim.cmd 'MoltenEvaluateOperator'
local keys = vim.api.nvim_replace_termcodes('ip', true, false, true)
vim.api.nvim_feedkeys(keys, 'n', false)
-- Move to next cell
vim.fn.search('^# %%', 'W')
end, { desc = 'Run Cell and Next', silent = true })

-- Cell navigation
keymap('n', ']c', ':MoltenNext<CR>', { desc = 'Next Cell', silent = true })
keymap('n', '[c', ':MoltenPrev<CR>', { desc = 'Previous Cell', silent = true })
end

-- Setup autocommands
local function setup_autocmds()
local augroup = vim.api.nvim_create_augroup('MoltenConfig', { clear = true })

-- Auto-initialize for Python files
vim.api.nvim_create_autocmd('FileType', {
group = augroup,
pattern = { 'python' },
callback = function()
vim.keymap.set('n', '<leader>ms', function()
vim.cmd 'MoltenInit python3'
vim.notify('Molten kernel started', vim.log.levels.INFO)
end, { buffer = true, desc = 'Start Python Kernel' })

-- Auto-detect notebook files with cell markers
vim.schedule(function()
local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false)
for _, line in ipairs(lines) do
if line:match '^# %%' then
vim.notify('Notebook detected. Use <leader>ms to start kernel', vim.log.levels.INFO)
break
end
end
end)
end,
})

-- Better notebook file handling
vim.api.nvim_create_autocmd('BufRead', {
group = augroup,
pattern = { '*.ipynb' },
callback = function()
-- Consider using jupytext for better .ipynb handling
vim.notify('Notebook file detected. Use :MoltenInit to start kernel', vim.log.levels.INFO)
end,
})
end

-- Main setup function
function M.setup()
-- Keybindings
local map = vim.keymap.set

map('n', '<leader>Mi', ':MoltenInit<CR>', { desc = 'Molten: Initialize' })
map('n', '<leader>Me', ':MoltenEvaluateOperator<CR>', { desc = 'Molten: Evaluate operator' })
map('v', '<leader>Me', ':<C-u>MoltenEvaluateVisual<CR>gv', { desc = 'Molten: Evaluate visual' })
map('n', '<leader>Ml', ':MoltenEvaluateLine<CR>', { desc = 'Molten: Evaluate line' })
map('n', '<leader>Mr', ':MoltenReevaluateCell<CR>', { desc = 'Molten: Re-evaluate cell' })
map('n', '<leader>Md', ':MoltenDelete<CR>', { desc = 'Molten: Delete cell' })
map('n', '<leader>Mo', ':MoltenShowOutput<CR>', { desc = 'Molten: Show output' })
map('n', '<leader>Mh', ':MoltenHideOutput<CR>', { desc = 'Molten: Hide output' })
map('n', '<leader>Mx', ':MoltenInterrupt<CR>', { desc = 'Molten: Interrupt kernel' })
-- Tell Molten about cell delimiters
vim.g.molten_cell_delimiter = '^# %%'

setup_keymaps()
setup_autocmds()
end

-- Initialize
M.setup()

return M
8 changes: 8 additions & 0 deletions lua/plugins/spec/image.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-- Render images in neovim
return {
'3rd/image.nvim',
build = false,
config = function()
require('plugins.config.image').setup()
end,
}
12 changes: 12 additions & 0 deletions lua/plugins/spec/jupytext.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-- Jupytext - Jupyter notebook as plain text
return {
'goerz/jupytext.vim',
ft = { 'ipynb', 'python', 'markdown', 'julia', 'r' },
build = function()
-- Ensure jupytext is installed
vim.fn.system('pip install jupytext')
end,
config = function()
require('plugins.config.jupytext')
end,
}
19 changes: 19 additions & 0 deletions lua/plugins/spec/molten.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-- Molten - Jupyter notebook support in Neovim
return {
'benlubas/molten-nvim',
version = '^1.0.0',
dependencies = { '3rd/image.nvim' },
build = ':UpdateRemotePlugins',
init = function()
-- Basic settings
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_wrap_output = true
vim.g.molten_virt_text_output = true
end,
config = function()
-- Delegate to config file for complex setup
require('plugins.config.molten')
end,
}