return {
	{
		"jmbuhr/otter.nvim",
		config = function()
			require("otter").setup({
				buffers = {
					set_filetype = true,
					write_to_disk = false,
				},
			})
		end,
	},
	{
		"quarto-dev/quarto-nvim",
		dependencies = {
			"jmbuhr/otter.nvim",
			"neovim/nvim-lspconfig",
			"hrsh7th/nvim-cmp",
			"nvim-treesitter/nvim-treesitter",
			{ "benlubas/molten-nvim", optional = true },
		},
		config = function()
			require("core.util").set_python_env()

			require("quarto").setup({
				lspFeatures = {
					enabled = true,
					languages = { "r", "python", "julia", "bash" },
				},
				codeRunner = {
					enabled = true,
					default_method = "slime",
					ft_runners = {
						python = "molten",
						quarto = "molten",
					},
				},
			})
			local map = vim.keymap.set
			map("n", "<localleader>C", require("quarto.runner").run_cell, { desc = "run cell" })
			map("n", "<localleader>ca", require("quarto.runner").run_above, { desc = "run cells above" })
			map("n", "<localleader>cb", require("quarto.runner").run_below, { desc = "run cells below" })
			map("n", "<localleader>cA", require("quarto.runner").run_all, { desc = "run all similar cells" })
			map("n", "]c", "/^```{<cr>}:nohl<cr>", { desc = "Codecell forward" })
			map("n", "[c", "?^```<cr>n}:nohl<cr>", { desc = "Codecell last" })
			map("n", "<localleader>co", "o```{python}<cr><cr>```<esc>k", { desc = "Insert quarto cell below" })
			map("n", "<localleader>cO", "O```{python}<cr><cr>```<esc>k", { desc = "Insert quarto cell above" })

			if require("core.util").is_available("which-key") then
				require("which-key").add({ "<localleader>c", group = "codecells" })
			end
		end,
		ft = { "quarto" },
	},

	{
		"vhyrro/luarocks.nvim",
		priority = 1001, -- this plugin needs to run before anything else
		opts = {
			rocks = { "magick" },
		},
	},
	-- image display
	{
		"3rd/image.nvim",
		dependencies = { "luarocks.nvim", { "nvim-treesitter/nvim-treesitter", optional = true } },
		cond = vim.fn.executable("magick") == 1, -- only runs if imagemagick installed
		config = function()
			local integrations = {}
			if vim.treesitter.language.get_lang("markdown") then
				integrations["markdown"] = {
					enabled = true,
					clear_in_insert_mode = true,
					download_remote_images = true,
					only_render_image_at_cursor = true,
					filetypes = { "markdown", "vimwiki", "quarto" },
				}
			end
			if vim.treesitter.language.get_lang("norg") then
				integrations["neorg"] = {
					enabled = true,
					clear_in_insert_mode = true,
					download_remote_images = true,
					only_render_image_at_cursor = true,
					filetypes = { "norg" },
				}
			end
			if next(integrations) ~= nil then -- only set up if we have at least 1 TS parser
				require("image").setup({
					backend = "kitty",
					integrations = integrations,
				})
				vim.g.molten_image_provider = "image.nvim"
				pcall(vim.fn.MoltenUpdateOption, "molten_image_provider", "image.nvim")
			end
		end,
		ft = { "markdown", "vimwiki", "quarto", "norg", "python" },
		priority = 60,
	},
	-- REPL work
	{
		"benlubas/molten-nvim",
		dependencies = {
			{ "willothy/wezterm.nvim", config = true },
			{ "3rd/image.nvim", optional = true },
		},
		build = ":UpdateRemotePlugins",
		config = function()
			vim.g.molten_image_provider = vim.g.molten_image_provider or "wezterm"
			vim.g.molten_auto_open_output = false
			vim.g.molten_virt_text_output = true
			if vim.fn.has("nvim-0.10") then
				vim.g.molten_output_show_more = true
			end
			vim.api.nvim_create_autocmd("User", {
				pattern = "MoltenInitPost",
				callback = function()
					local map = vim.keymap.set
					if require("core.util").is_available("which-key") then
						require("which-key").add({ "<localleader>c", group = "codecells" })
					end
					-- Operate jupyter notebooks from within vim
					map(
						"n",
						"<localleader>cc",
						":MoltenEvaluateOperator<cr>",
						{ desc = "evaluate operator", silent = true }
					)
					map("n", "<localleader>cl", ":MoltenEvaluateLine<cr>", { desc = "evaluate line", silent = true })
					map(
						"x",
						"<localleader>c",
						":<C-u>MoltenEvaluateVisual<cr>",
						{ desc = "evaluate visual", silent = true }
					)
					map(
						"n",
						"<localleader>cr",
						":MoltenReevaluateCell<cr>",
						{ desc = "reevaluate cell", silent = true }
					)
					map(
						"n",
						"<localleader>cp",
						":noautocmd :MoltenEnterOutput<cr>",
						{ silent = true, desc = "show output" }
					)
					map("n", "<localleader>cP", function()
						vim.cmd("MoltenHideOutput")
						vim.cmd("MoltenDelete")
					end, { silent = true, desc = "hide output" })
					map("n", "<localleader>ci", ":MoltenImagePopup<cr>", { silent = true, desc = "open image" })
					map("n", "<localleader>cI", ":MoltenInterrupt<cr>", { desc = "interrupt cell", silent = true })
					map("n", "<localleader>cD", ":MoltenDeinit<cr>", { desc = "de-init molten", silent = true })
					map("n", "<localleader>cR", ":MoltenRestart<cr>", { desc = "restart molten", silent = true })
					-- FIXME: Works for toggling TO virt text but not back
					local function toggle_virtual_text_output()
						if vim.g.molten_virt_text_output then
							vim.fn.MoltenUpdateOption("molten_virt_text_output", false)
							return
						end
						vim.fn.MoltenUpdateOption("molten_virt_text_output", true)
					end
					map(
						"n",
						"<localleader>cV",
						toggle_virtual_text_output,
						{ desc = "toggle virtual output", silent = true }
					)
				end,
			})
			vim.api.nvim_create_autocmd("User", {
				pattern = "MoltenDeinitPost",
				callback = function()
					local unmap = require("core.util").unmap_key
					unmap("<localleader>cc")
					unmap("<localleader>cl")
					unmap("<localleader>c", "x")
					unmap("<localleader>cr")
					unmap("<localleader>cR")
					unmap("<localleader>cp")
					unmap("<localleader>cP")
					unmap("<localleader>co")
					unmap("<localleader>cD")
					unmap("<localleader>ci")
					unmap("<localleader>cV")
					local map = vim.keymap.set
					map("n", "<localleader>cJ", ":JupyterStart<cr>", { desc = "start jupyter", silent = true })
				end,
			})
		end,
		ft = { "norg", "quarto", "python" },
		keys = {
			{ "<leader>vn", ":MoltenInfo<cr>" },
			{ "<localleader>cJ", ":JupyterStart<cr>", desc = "start jupyter", silent = true },
		},
		cmd = {
			"MoltenInfo",
			"MoltenInit",
			"MoltenDeinit",
			"MoltenGoto",
			"MoltenNext",
			"MoltenPrev",
			"MoltenEvaluateLine",
			"MoltenEvaluateVisual",
			"MoltenEvaluateOperator",
			"MoltenEvaluateArgument",
			"MoltenReevaluateCell",
			"MoltenDelete",
			"MoltenShowOutput",
			"MoltenHideOutput",
			"MoltenEnterOutput",
			"MoltenInterrupt",
			"MoltenOpenInBrowser",
			"MoltenImagePopup",
			"MoltenRestart",
			"MoltenSave",
			"MoltenLoad",
			"MoltenExportOutput",
			"MoltenImportOutput",
		},
		lazy = false,
	},

	-- Edit code blocks in md/quarto using whatever language is
	{
		"AckslD/nvim-FeMaco.lua",
		cmd = {
			"FeMaco",
		},
		ft = { "markdown", "quarto" },
		opts = {},
		dependencies = {
			"nvim-treesitter/nvim-treesitter",
		},
		keys = {
			{ "<localleader>ce", ":FeMaco<cr>", desc = "edit codecell" },
		},
	},
	-- MARKDOWN ONLY
	-- Evaluate markdown code blocks
	{
		"jubnzv/mdeval.nvim",
		cmd = {
			"MdEval",
		},
		ft = { "markdown" },
		opts = {
			require_confirmation = false,
			eval_options = {},
		},
		lazy = false,
	},

	-- Open ipynb Jupyter notebooks as if they're quarto files
	-- requires jupytext to be installed
	{
		"GCBallesteros/jupytext.nvim",
		opts = {
			style = "light",
			custom_language_formatting = {
				python = {
					extension = "qmd",
					style = "quarto",
					force_ft = "quarto",
				},
				r = {
					extension = "qmd",
					style = "quarto",
					force_ft = "quarto",
				},
			},
		},
		cond = vim.fn.executable("jupytext") == 1, -- only runs if imagemagick installed
		lazy = false, -- does not work in lazy mode
	},
}
