├── after └── queries │ ├── r │ └── textobjects.scm │ ├── bash │ └── textobjects.scm │ ├── python │ └── textobjects.scm │ └── markdown │ └── textobjects.scm ├── doc ├── tags └── quarto.txt ├── examples ├── example3.qmd ├── example2.qmd └── example.qmd ├── .gitignore ├── stylua.toml ├── lua └── quarto │ ├── runner │ ├── slime.lua │ ├── molten.lua │ ├── iron.lua │ └── init.lua │ ├── tools.lua │ ├── config.lua │ ├── util.lua │ └── init.lua ├── ftplugin └── quarto.lua ├── plugin └── quarto.lua ├── LICENSE ├── .github ├── workflows │ └── ci.yml └── CODE_OF_CONDUCT.md ├── README.md └── CHANGELOG.md /after/queries/r/textobjects.scm: -------------------------------------------------------------------------------- 1 | ; extends 2 | ( 3 | (comment) @codechunk.inner 4 | (#eq? @codechunk.inner "#%%") 5 | ) 6 | -------------------------------------------------------------------------------- /after/queries/bash/textobjects.scm: -------------------------------------------------------------------------------- 1 | ; extends 2 | ( 3 | (comment) @codechunk.inner 4 | (#eq? @codechunk.inner "#%%") 5 | ) 6 | -------------------------------------------------------------------------------- /after/queries/python/textobjects.scm: -------------------------------------------------------------------------------- 1 | ; extends 2 | ( 3 | (comment) @codechunk.inner 4 | (#eq? @codechunk.inner "#%%") 5 | ) 6 | -------------------------------------------------------------------------------- /after/queries/markdown/textobjects.scm: -------------------------------------------------------------------------------- 1 | ; extends 2 | (code_fence_content) @codechunk.inner 3 | 4 | (fenced_code_block) @codechunk.outer 5 | -------------------------------------------------------------------------------- /doc/tags: -------------------------------------------------------------------------------- 1 | QuartoPreview quarto.txt /*QuartoPreview* 2 | quarto.nvim quarto.txt /*quarto.nvim* 3 | quarto.quartoPreview() quarto.txt /*quarto.quartoPreview()* 4 | -------------------------------------------------------------------------------- /examples/example3.qmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: Hello world 3 | format: lua 4 | --- 5 | 6 | ```{lua} 7 | local x = 1 8 | local y = 2 9 | local z = x + y 10 | ``` 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | example_files/ 2 | example.html 3 | example*.R 4 | example*.py 5 | example*.hs 6 | examples/*.docx 7 | examples/*.html 8 | examples/*.pdf 9 | .DS_Store 10 | -------------------------------------------------------------------------------- /stylua.toml: -------------------------------------------------------------------------------- 1 | column_width = 160 2 | line_endings = "Unix" 3 | indent_type = "Spaces" 4 | indent_width = 2 5 | quote_style = "AutoPreferSingle" 6 | call_parentheses = "None" 7 | [sort_requires] 8 | enabled = true 9 | -------------------------------------------------------------------------------- /examples/example2.qmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: Hello world 3 | format: html 4 | --- 5 | 6 | # Hello 7 | 8 | ```{haskell} 9 | xs :: [Int] 10 | xs = [x | x <- [1..10]] 11 | ``` 12 | 13 | 14 | ```{haskell} 15 | s :: Int 16 | s = sum xs 17 | ``` 18 | -------------------------------------------------------------------------------- /lua/quarto/runner/slime.lua: -------------------------------------------------------------------------------- 1 | local concat = require('quarto.tools').concat 2 | 3 | ---Run the code cell with slime 4 | ---@param cell CodeCell 5 | ---@param _ boolean 6 | local function run(cell, _) 7 | local text_lines = concat(cell.text) 8 | vim.fn['slime#send'](text_lines) 9 | end 10 | 11 | ---@class CodeRunner 12 | local M = { run = run } 13 | 14 | return M 15 | -------------------------------------------------------------------------------- /ftplugin/quarto.lua: -------------------------------------------------------------------------------- 1 | vim.b.slime_cell_delimiter = '```' 2 | 3 | -- TODO: Workaround while nvim-treesitter doesn't link those anymore 4 | -- until our ouwn pandoc grammar is ready 5 | vim.treesitter.language.register('markdown', { 'quarto', 'rmd' }) 6 | 7 | local config = require('quarto.config').config 8 | local quarto = require 'quarto' 9 | 10 | if config.lspFeatures.enabled then 11 | quarto.activate() 12 | end 13 | -------------------------------------------------------------------------------- /lua/quarto/runner/molten.lua: -------------------------------------------------------------------------------- 1 | ---Run the code cell with molten 2 | ---@param cell CodeCell 3 | ---@param ignore_cols boolean 4 | local function run(cell, ignore_cols) 5 | local range = cell.range 6 | if ignore_cols then 7 | vim.fn.MoltenEvaluateRange(range.from[1] + 1, range.to[1]) 8 | else 9 | vim.fn.MoltenEvaluateRange(range.from[1] + 1, range.to[1], range.from[2] + 1, range.to[2] + 1) 10 | end 11 | end 12 | 13 | ---@class CodeRunner 14 | local M = { run = run } 15 | 16 | return M 17 | -------------------------------------------------------------------------------- /lua/quarto/tools.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | M.contains = function(list, x) 4 | for _, v in pairs(list) do 5 | if v == x then 6 | return true 7 | end 8 | end 9 | return false 10 | end 11 | 12 | M.replace_header_div = function(response) 13 | response.contents = response.contents:gsub('
', '') 14 | return response 15 | end 16 | 17 | M.concat = function(ls) 18 | if type(ls) ~= 'table' then 19 | return ls .. '\n\n' 20 | end 21 | local s = '' 22 | for _, l in ipairs(ls) do 23 | if l ~= '' then 24 | s = s .. '\n' .. l 25 | end 26 | end 27 | return s .. '\n' 28 | end 29 | 30 | return M 31 | -------------------------------------------------------------------------------- /lua/quarto/runner/iron.lua: -------------------------------------------------------------------------------- 1 | local concat = require('quarto.tools').concat 2 | local iron_send = require('iron.core').send 3 | 4 | ---Run the code cell with iron 5 | ---@param cell CodeCell 6 | ---@param _ boolean 7 | local function run(cell, _) 8 | local text_lines = concat(cell.text) 9 | local lang = cell.lang or 'quarto' 10 | -- forward to iron.send 11 | -- first arg is filetype. if not supplied, iron.core.send would infer "quarto". 12 | -- Iron lets the user map a filetype to a repl binary, e.g. {"python" = "ipython", "r" = "radian"} 13 | -- so we can pass the cell.lang to get the same feel from a .qmd file. 14 | iron_send(lang, text_lines) 15 | end 16 | 17 | ---@class CodeRunner 18 | local M = { run = run } 19 | 20 | return M 21 | -------------------------------------------------------------------------------- /lua/quarto/config.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | M.defaultConfig = { 4 | debug = false, 5 | closePreviewOnExit = true, 6 | lspFeatures = { 7 | enabled = true, 8 | chunks = 'curly', 9 | languages = nil, -- nil means all languages found in the document 10 | diagnostics = { 11 | enabled = true, 12 | triggers = { 'BufWritePost' }, 13 | }, 14 | completion = { 15 | enabled = true, 16 | }, 17 | }, 18 | codeRunner = { 19 | enabled = true, 20 | default_method = 'slime', -- "molten", "slime", "iron" or 21 | ft_runners = {}, -- filetype to runner, ie. `{ python = "molten" }`. 22 | -- Takes precedence over `default_method` 23 | never_run = { 'yaml' }, -- filetypes which are never sent to a code runner 24 | }, 25 | } 26 | 27 | -- use defaultConfig if not setup 28 | M.config = M.config or M.defaultConfig 29 | 30 | return M 31 | -------------------------------------------------------------------------------- /examples/example.qmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: Hello World 3 | format: html 4 | --- 5 | 6 | # Hello World 7 | 8 | This is some python code, in which we define a function `hello`: 9 | 10 | ```{python} 11 | def hello(): 12 | print("Hello") 13 | ``` 14 | 15 | ```bash 16 | git clone 17 | ``` 18 | 19 | This is how we call it: 20 | 21 | ```{python} 22 | hello() 23 | ``` 24 | 25 | ```haskell 26 | xs = [x | x <- [1..10]] 27 | ``` 28 | 29 | We can press `gd` to go to its definition. 30 | 31 | Let's demonstrate the autocompletion: 32 | 33 | ```{python} 34 | import numpy as np 35 | ``` 36 | 37 | And code hovers with `K`. 38 | 39 | # Furthermore 40 | 41 | And then we can work on integrating this for multiple 42 | languages in the same document, like this R function: 43 | 44 | ```{r} 45 | hello_from_r <- function() { 46 | print("hello") 47 | } 48 | ``` 49 | 50 | Unsurprisingly, the function does the same thing: 51 | 52 | ```{r} 53 | hello_from_r() 54 | ``` 55 | 56 | 57 | ```{r} 58 | library(tidyverse) 59 | ``` 60 | 61 | -------------------------------------------------------------------------------- /plugin/quarto.lua: -------------------------------------------------------------------------------- 1 | if vim.fn.has 'nvim-0.9.0' ~= 1 then 2 | local msg = [[ 3 | quarto-dev/quarto-nvim and jmbuhr/otter.nvim require Neovim version >= 0.9.0 (https://github.com/neovim/neovim/releases/tag/stable). 4 | If you are unable to update Neovim, you can specify a specific version of the plugins involved instead of the latest stable version. 5 | How you do this will vary depending on your plugin manager, but you can see one example using `lazy.nvim` here: 6 | 7 | ]] 8 | vim.notify_once(msg, vim.log.levels.WARN) 9 | end 10 | 11 | vim.api.nvim_create_user_command('QuartoPreview', require('quarto').quartoPreview, { nargs = '*' }) 12 | vim.api.nvim_create_user_command('QuartoPreviewNoWatch', require('quarto').quartoPreviewNoWatch, { nargs = '*' }) 13 | vim.api.nvim_create_user_command('QuartoUpdatePreview', require('quarto').quartoUpdatePreview, { nargs = '*' }) 14 | vim.api.nvim_create_user_command('QuartoClosePreview', require('quarto').quartoClosePreview, {}) 15 | vim.api.nvim_create_user_command('QuartoActivate', require('quarto').activate, {}) 16 | vim.api.nvim_create_user_command('QuartoHelp', require('quarto').searchHelp, { nargs = 1 }) 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | quarto-nvim 2 | Copyright (C) 2022-2023 Posit Software, PBC 3 | 4 | With the exceptions noted below, this code is released under the 5 | [GPL](http://www.gnu.org/copyleft/gpl.html), version 2 or later: 6 | 7 | This program is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation; either version 2 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | 21 | The GNU General Public License is available in the file COPYING.md in 22 | the source distribution. On Debian systems, the complete text of the 23 | GPL can be found in `/usr/share/common-licenses/GPL`. 24 | 25 | Quarto-nvim's source code is at https://github.com/quarto-dev/quarto-nvim 26 | -------------------------------------------------------------------------------- /lua/quarto/util.lua: -------------------------------------------------------------------------------- 1 | -- functions from neovim/nvim-lspconfig 2 | local M = {} 3 | 4 | local nvim_eleven = vim.fn.has 'nvim-0.11' == 1 5 | 6 | function M.tbl_flatten(t) 7 | --- @diagnostic disable-next-line:deprecated 8 | return nvim_eleven and vim.iter(t):flatten(math.huge):totable() or vim.tbl_flatten(t) 9 | end 10 | 11 | function M.strip_archive_subpath(path) 12 | -- Matches regex from zip.vim / tar.vim 13 | path = vim.fn.substitute(path, 'zipfile://\\(.\\{-}\\)::[^\\\\].*$', '\\1', '') 14 | path = vim.fn.substitute(path, 'tarfile:\\(.\\{-}\\)::.*$', '\\1', '') 15 | return path 16 | end 17 | 18 | function M.search_ancestors(startpath, func) 19 | if nvim_eleven then 20 | vim.validate('func', func, 'function') 21 | end 22 | if func(startpath) then 23 | return startpath 24 | end 25 | local guard = 100 26 | for path in vim.fs.parents(startpath) do 27 | -- Prevent infinite recursion if our algorithm breaks 28 | guard = guard - 1 29 | if guard == 0 then 30 | return 31 | end 32 | 33 | if func(path) then 34 | return path 35 | end 36 | end 37 | end 38 | 39 | local function escape_wildcards(path) 40 | return path:gsub('([%[%]%?%*])', '\\%1') 41 | end 42 | 43 | function M.root_pattern(...) 44 | local patterns = M.tbl_flatten { ... } 45 | return function(startpath) 46 | startpath = M.strip_archive_subpath(startpath) 47 | for _, pattern in ipairs(patterns) do 48 | local match = M.search_ancestors(startpath, function(path) 49 | for _, p in ipairs(vim.fn.glob(table.concat({ escape_wildcards(path), pattern }, '/'), true, true)) do 50 | if vim.uv.fs_stat(p) then 51 | return path 52 | end 53 | end 54 | end) 55 | 56 | if match ~= nil then 57 | return match 58 | end 59 | end 60 | end 61 | end 62 | 63 | return M 64 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | pull_request: 5 | workflow_dispatch: 6 | 7 | jobs: 8 | tests: 9 | strategy: 10 | matrix: 11 | # os: [ubuntu-latest, windows-latest] 12 | os: [ubuntu-latest] 13 | runs-on: ${{ matrix.os }} 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Install Neovim 17 | shell: bash 18 | run: | 19 | sudo apt install libfuse2 20 | sudo wget -q https://github.com/neovim/neovim/releases/download/stable/nvim-linux-x86_64.appimage -O /usr/bin/nvim 21 | sudo chmod 0755 /usr/bin/nvim 22 | - name: Run Tests 23 | run: | 24 | nvim --version 25 | [ ! -d tests ] && exit 0 26 | nvim --headless -u tests/init.lua -c "PlenaryBustedDirectory tests/ {minimal_init = 'tests/init.lua', sequential = true}" 27 | docs: 28 | runs-on: ubuntu-latest 29 | needs: tests 30 | if: ${{ github.ref == 'refs/heads/main' }} 31 | steps: 32 | - uses: actions/checkout@v3 33 | - name: panvimdoc 34 | uses: kdheepak/panvimdoc@main 35 | with: 36 | vimdoc: quarto 37 | version: "Neovim >= 0.8.0" 38 | demojify: true 39 | treesitter: true 40 | - name: Push changes 41 | uses: stefanzweifel/git-auto-commit-action@v4 42 | with: 43 | commit_message: "chore(build): auto-generate vimdoc" 44 | commit_user_name: "github-actions[bot]" 45 | commit_user_email: "github-actions[bot]@users.noreply.github.com" 46 | commit_author: "github-actions[bot] " 47 | release: 48 | name: release 49 | if: ${{ github.ref == 'refs/heads/main' }} 50 | needs: 51 | - docs 52 | - tests 53 | runs-on: ubuntu-latest 54 | steps: 55 | - uses: google-github-actions/release-please-action@v3 56 | id: release 57 | with: 58 | release-type: simple 59 | package-name: quarto-nvim 60 | - uses: actions/checkout@v2 61 | - name: tag stable versions 62 | if: ${{ steps.release.outputs.release_created }} 63 | run: | 64 | git config user.name github-actions[bot] 65 | git config user.email github-actions[bot]@users.noreply.github.com 66 | git remote add gh-token "https://${{ secrets.GITHUB_TOKEN }}@github.com/google-github-actions/release-please-action.git" 67 | git tag -d stable || true 68 | git push origin :stable || true 69 | git tag -a stable -m "Last Stable Release" 70 | git push origin stable 71 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | codeofconduct@rstudio.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /lua/quarto/runner/init.lua: -------------------------------------------------------------------------------- 1 | --- Code runner, configurable to use different engines. 2 | local Runner = {} 3 | 4 | local otterkeeper = require 'otter.keeper' 5 | local config = require('quarto.config').config 6 | 7 | local no_code_found = 'No code chunks found for the current language, which is detected based on the current code block. Is your cursor in a code block?' 8 | 9 | local function overlaps_range(range, other) 10 | return range.from[1] <= other.to[1] and other.from[1] <= range.to[1] 11 | end 12 | 13 | ---pull the code chunks that overlap the given range, removes cells with a language that's in the 14 | ---ignore list 15 | ---@param lang string? 16 | ---@param code_chunks table 17 | ---@param range CodeRange 18 | ---@return CodeCell[] 19 | local function extract_code_cells_in_range(lang, code_chunks, range) 20 | local chunks = {} 21 | 22 | if lang then 23 | for _, chunk in ipairs(code_chunks[lang]) do 24 | if overlaps_range(chunk.range, range) then 25 | table.insert(chunks, chunk) 26 | end 27 | end 28 | else 29 | for l, lang_chunks in pairs(code_chunks) do 30 | if vim.tbl_contains(config.codeRunner.never_run, l) then 31 | goto continue 32 | end 33 | for _, chunk in ipairs(lang_chunks) do 34 | if overlaps_range(chunk.range, range) then 35 | table.insert(chunks, chunk) 36 | end 37 | end 38 | ::continue:: 39 | end 40 | end 41 | 42 | return chunks 43 | end 44 | 45 | -- type `Range` is already defined twice in nvim core 46 | ---@class CodeRange 47 | ---@field from table 48 | ---@field to table 49 | 50 | ---@class CodeCell 51 | ---@field lang string? 52 | ---@field text table 53 | ---@field range CodeRange 54 | 55 | ---@class CodeRunner 56 | ---@field run function 57 | 58 | ---send code cell to the correct repl based on language, and user configuration 59 | ---@param cell CodeCell 60 | ---@param opts table? 61 | local function send(cell, opts) 62 | opts = opts or { ignore_cols = false } 63 | local runner = config.codeRunner.default_method 64 | local ft_runners = config.codeRunner.ft_runners 65 | if cell.lang ~= nil and ft_runners[cell.lang] ~= nil then 66 | runner = ft_runners[cell.lang] 67 | end 68 | 69 | -- if user passes a fn to config.codeRunner.default_method, we use that. 70 | -- (this also means fns are allowed as values in ft_runners) 71 | -- otherwise we lookup a string for pre-packaged runner function, e.g. "molten" 72 | if type(runner) == 'function' then 73 | runner(cell, opts.ignore_cols) 74 | elseif type(runner) == 'string' then 75 | require('quarto.runner.' .. runner).run(cell, opts.ignore_cols) 76 | else 77 | vim.notify("[Quarto] couldn't find appropriate code runner for language: " .. cell.lang, vim.log.levels.ERROR) 78 | end 79 | end 80 | 81 | ---run the code chunks for the given language that overlap the given range 82 | ---@param range CodeRange a range, for with any overlapping code cells are run 83 | ---@param multi_lang boolean? 84 | local function run(range, multi_lang) 85 | local buf = vim.api.nvim_get_current_buf() 86 | local lang = nil 87 | if not multi_lang then 88 | lang = otterkeeper.get_current_language_context() 89 | end 90 | 91 | otterkeeper.sync_raft(buf) 92 | local oa = otterkeeper.rafts[buf] 93 | if oa == nil then 94 | vim.notify("[Quarto] it seems that the code runner isn't initialized for this buffer.", vim.log.levels.ERROR) 95 | return 96 | end 97 | local chunks = otterkeeper.rafts[buf].code_chunks 98 | 99 | local filtered = extract_code_cells_in_range(lang, chunks, range) 100 | 101 | if #filtered == 0 then 102 | print(no_code_found) 103 | return 104 | end 105 | for _, chunk in ipairs(filtered) do 106 | send(chunk, { ignore_cols = true }) 107 | end 108 | end 109 | 110 | ---@param multi_lang boolean? 111 | Runner.run_cell = function(multi_lang) 112 | local y = vim.api.nvim_win_get_cursor(0)[1] - 1 113 | local r = { y, 0 } 114 | local range = { from = r, to = r } 115 | 116 | run(range, multi_lang) 117 | end 118 | 119 | ---@param multi_lang boolean? 120 | Runner.run_above = function(multi_lang) 121 | local y = vim.api.nvim_win_get_cursor(0)[1] - 1 122 | local range = { from = { 0, 0 }, to = { y, 0 } } 123 | 124 | run(range, multi_lang) 125 | end 126 | 127 | ---@param multi_lang boolean? 128 | Runner.run_below = function(multi_lang) 129 | local y = vim.api.nvim_win_get_cursor(0)[1] - 1 130 | local range = { from = { y, 0 }, to = { math.huge, 0 } } 131 | 132 | run(range, multi_lang) 133 | end 134 | 135 | ---@param multi_lang boolean? 136 | Runner.run_all = function(multi_lang) 137 | local range = { from = { 0, 0 }, to = { math.huge, 0 } } 138 | 139 | run(range, multi_lang) 140 | end 141 | 142 | Runner.run_line = function() 143 | local buf = vim.api.nvim_get_current_buf() 144 | local lang = otterkeeper.get_current_language_context() 145 | local pos = vim.api.nvim_win_get_cursor(0) 146 | 147 | ---@type CodeCell 148 | local cell = { 149 | lang = lang, 150 | range = { from = { pos[1] - 1, 0 }, to = { pos[1], 0 } }, 151 | text = vim.api.nvim_buf_get_lines(buf, pos[1] - 1, pos[1], false), 152 | } 153 | 154 | send(cell, { ignore_cols = true }) 155 | end 156 | 157 | -- NOTE: This function will not work the same with molten as it does with slime/iron. Generally, code 158 | -- runners which run code based on the CodeCell range field, will not work when the user selects 159 | -- code across cells. But it will work if a selection is entirely within a cell. 160 | -- Also: This function cannot run multiple languages at once. 161 | Runner.run_range = function() 162 | local lines = otterkeeper.get_language_lines_in_visual_selection(true) 163 | if lines == nil then 164 | print(no_code_found) 165 | return 166 | end 167 | 168 | local vstart = vim.fn.getpos "'<" 169 | local vend = vim.fn.getpos "'>" 170 | 171 | if vstart and vend then 172 | local range = { from = { vstart[2] - 1, vstart[1] }, to = { vend[2], vend[1] } } 173 | send { lang = otterkeeper.get_current_language_context(), range = range, text = lines } 174 | else 175 | print 'No visual selection' 176 | end 177 | end 178 | 179 | return Runner 180 | -------------------------------------------------------------------------------- /lua/quarto/init.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | local api = vim.api 3 | local cfg = require 'quarto.config' 4 | local tools = require 'quarto.tools' 5 | local util = require 'quarto.util' 6 | 7 | ---Quarto preview 8 | ---@param opts table 9 | ---@return nil|string url 10 | function M.quartoPreview(opts) 11 | opts = opts or {} 12 | local args = opts.args or '' 13 | 14 | -- Find root directory / check if it is a project 15 | local buffer_path = api.nvim_buf_get_name(0) 16 | local root_dir = util.root_pattern '_quarto.yml'(buffer_path) 17 | local cmd 18 | local mode 19 | 20 | -- check for 21 | -- 22 | -- editor: 23 | -- render-on-save: false 24 | -- 25 | -- in _quarto.yml or the current qmd file 26 | 27 | local render_on_save = true 28 | 29 | local lines 30 | if root_dir then 31 | local quarto_config = root_dir .. '/_quarto.yml' 32 | lines = vim.fn.readfile(quarto_config) 33 | else 34 | -- assumption: the yaml header is not longer than a generous 500 lines 35 | lines = vim.api.nvim_buf_get_lines(0, 0, 500, false) 36 | end 37 | 38 | local query = 'render%-on%-save: false' 39 | for _, line in ipairs(lines) do 40 | if line:find(query) then 41 | render_on_save = false 42 | break 43 | end 44 | end 45 | 46 | if not render_on_save and string.find(args, '%-%-no%-watch%-inputs') == nil then 47 | args = args .. ' --no-watch-inputs' 48 | end 49 | 50 | if root_dir then 51 | mode = 'project' 52 | cmd = 'quarto preview ' .. vim.fn.shellescape(root_dir) .. ' ' .. args 53 | else 54 | mode = 'file' 55 | cmd = 'quarto preview ' .. vim.fn.shellescape(buffer_path) .. ' ' .. args 56 | end 57 | 58 | -- Check file extensions 59 | local quarto_extensions = { '.qmd', '.Rmd', '.ipynb', '.md' } 60 | local file_extension = buffer_path:match '^.+(%..+)$' 61 | if mode == 'file' and not file_extension then 62 | vim.notify 'Not in a file. exiting.' 63 | return 64 | end 65 | if mode == 'file' and not tools.contains(quarto_extensions, file_extension) then 66 | vim.notify('Not a quarto file, ends in ' .. file_extension .. ' exiting.') 67 | return 68 | end 69 | 70 | -- Store current tabpage 71 | local current_tabpage = vim.api.nvim_get_current_tabpage() 72 | 73 | -- Open a new tab for the terminal 74 | vim.cmd 'tabnew' 75 | local term_buf = vim.api.nvim_create_buf(true, false) 76 | vim.api.nvim_set_current_buf(term_buf) 77 | 78 | vim.fn.termopen(cmd, { 79 | on_exit = function(_, exit_code, _) 80 | if exit_code ~= 0 then 81 | vim.notify('Quarto preview exited with code ' .. exit_code, vim.log.levels.ERROR) 82 | end 83 | end, 84 | }) 85 | 86 | -- Store the terminal buffer and return to previous tab 87 | local quartoOutputBuf = vim.api.nvim_get_current_buf() 88 | 89 | -- go back to the previous tab 90 | vim.api.nvim_set_current_tabpage(current_tabpage) 91 | api.nvim_buf_set_var(0, 'quartoOutputBuf', quartoOutputBuf) 92 | 93 | -- Close preview terminal on exit of the Quarto buffer 94 | if cfg.config and cfg.config.closePreviewOnExit then 95 | api.nvim_create_autocmd({ 'QuitPre', 'WinClosed' }, { 96 | buffer = api.nvim_get_current_buf(), 97 | group = api.nvim_create_augroup('quartoPreview', {}), 98 | callback = function(_, _) 99 | if api.nvim_buf_is_loaded(quartoOutputBuf) then 100 | api.nvim_buf_delete(quartoOutputBuf, { force = true }) 101 | end 102 | end, 103 | }) 104 | end 105 | end 106 | 107 | function M.quartoPreviewNoWatch() 108 | M.quartoPreview { args = '--no-watch-inputs' } 109 | end 110 | 111 | function M.quartoUpdatePreview() 112 | local quartoOutputBuf = api.nvim_buf_get_var(0, 'quartoOutputBuf') 113 | local query_start = 'Browse at http' 114 | local lines = vim.api.nvim_buf_get_lines(quartoOutputBuf, 0, -1, false) 115 | local url = nil 116 | for _, line in ipairs(lines) do 117 | if line:find(query_start) then 118 | url = 'http' .. line:sub(#query_start + 1) 119 | break 120 | end 121 | end 122 | if not url then 123 | vim.notify('Could not find the preview url in the terminal buffer. Maybe it is still warming up. Check the buffer and try again.', vim.log.levels.WARN) 124 | return 125 | end 126 | api.nvim_buf_set_var(0, 'quartoUrl', url) 127 | local request_url = url .. 'quarto-render/' 128 | local get_request = 'curl -s ' .. request_url 129 | local response = vim.fn.system(get_request) 130 | if response ~= 'rendered' then 131 | vim.notify_once('Failed to update preview with command: ' .. get_request, vim.log.levels.ERROR) 132 | end 133 | end 134 | 135 | function M.quartoClosePreview() 136 | local success, quartoOutputBuf = pcall(api.nvim_buf_get_var, 0, 'quartoOutputBuf') 137 | if not success then 138 | return 139 | end 140 | if api.nvim_buf_is_loaded(quartoOutputBuf) then 141 | api.nvim_buf_delete(quartoOutputBuf, { force = true }) 142 | end 143 | end 144 | 145 | M.searchHelp = function(cmd_input) 146 | local topic = cmd_input.args 147 | local url = 'https://quarto.org/?q=' .. topic .. '&show-results=1' 148 | local sysname = vim.loop.os_uname().sysname 149 | local cmd 150 | if sysname == 'Linux' then 151 | cmd = 'xdg-open "' .. url .. '"' 152 | elseif sysname == 'Darwin' then 153 | cmd = 'open "' .. url .. '"' 154 | else 155 | print 'sorry, I do not know how to make Windows open a url with the default browser. This feature currently only works on linux and mac.' 156 | return 157 | end 158 | vim.fn.jobstart(cmd) 159 | end 160 | 161 | -- from https://github.com/neovim/nvim-lspconfig/blob/f98fa715acc975c2dd5fb5ba7ceddeb1cc725ad2/lua/lspconfig/util.lua#L23 162 | function M.bufname_valid(bufname) 163 | if bufname:match '^/' or bufname:match '^[a-zA-Z]:' or bufname:match '^zipfile://' or bufname:match '^tarfile:' then 164 | return true 165 | end 166 | return false 167 | end 168 | 169 | M.activate = function() 170 | local bufname = vim.api.nvim_buf_get_name(0) 171 | -- do not activate in special buffers, for example 'fugitive://...' 172 | if not M.bufname_valid(bufname) then 173 | return 174 | end 175 | local tsquery = nil 176 | if cfg.config.lspFeatures.chunks == 'curly' then 177 | tsquery = [[ 178 | (fenced_code_block 179 | (info_string 180 | (language) @_lang 181 | ) @info 182 | (#match? @info "{") 183 | (code_fence_content) @content (#offset! @content) 184 | ) 185 | ((html_block) @html @combined) 186 | 187 | ((minus_metadata) @yaml (#offset! @yaml 1 0 -1 0)) 188 | ((plus_metadata) @toml (#offset! @toml 1 0 -1 0)) 189 | 190 | ]] 191 | end 192 | require('otter').activate(cfg.config.lspFeatures.languages, cfg.config.lspFeatures.completion.enabled, cfg.config.lspFeatures.diagnostics.enabled, tsquery) 193 | end 194 | 195 | -- setup 196 | M.setup = function(opt) 197 | cfg.config = vim.tbl_deep_extend('force', cfg.defaultConfig, opt or {}) 198 | 199 | if cfg.config.codeRunner.enabled then 200 | -- setup top level run functions 201 | local runner = require 'quarto.runner' 202 | M.quartoSend = runner.run_cell 203 | M.quartoSendAbove = runner.run_above 204 | M.quartoSendBelow = runner.run_below 205 | M.quartoSendAll = runner.run_all 206 | M.quartoSendRange = runner.run_range 207 | M.quartoSendLine = runner.run_line 208 | 209 | -- setup run user commands 210 | api.nvim_create_user_command('QuartoSend', function(_) 211 | runner.run_cell() 212 | end, {}) 213 | api.nvim_create_user_command('QuartoSendAbove', function(_) 214 | runner.run_above() 215 | end, {}) 216 | api.nvim_create_user_command('QuartoSendBelow', function(_) 217 | runner.run_below() 218 | end, {}) 219 | api.nvim_create_user_command('QuartoSendAll', function(_) 220 | runner.run_all() 221 | end, {}) 222 | api.nvim_create_user_command('QuartoSendRange', function(_) 223 | runner.run_range() 224 | end, { range = 2 }) 225 | api.nvim_create_user_command('QuartoSendLine', function(_) 226 | runner.run_line() 227 | end, {}) 228 | end 229 | end 230 | 231 | return M 232 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # quarto-nvim 2 | 3 | Quarto-nvim provides tools for working on [Quarto](https://quarto.org/) manuscripts in Neovim. 4 | You can get started with Quarto [here](https://quarto.org/docs/get-started/). 5 | 6 | ## Walkthrough 7 | 8 | The [get started section](https://quarto.org/docs/get-started/hello/neovim.html) also comes with a video version to walk you through. 9 | The playlist is extended as more features are added, so join us for a "Coffee with Quarto and Neovim": 10 | 11 | 12 | 13 | ## Setup 14 | 15 | You can install `quarto-nvim` from GitHub with your favourite Neovim plugin manager 16 | like [lazy.nvim](https://github.com/folke/lazy.nvim), 17 | [packer.nvim](https://github.com/wbthomason/packer.nvim) or [VimPlug](https://github.com/junegunn/vim-plug). 18 | 19 | Example `lazy.nvim` plugin specification: 20 | 21 | ```lua 22 | -- plugins/quarto.lua 23 | return { 24 | { 25 | "quarto-dev/quarto-nvim", 26 | dependencies = { 27 | "jmbuhr/otter.nvim", 28 | "nvim-treesitter/nvim-treesitter", 29 | }, 30 | }, 31 | } 32 | ``` 33 | 34 | Because Quarto provides a lot of functionality through integration with existing plugins, 35 | you may want to experiment with my [config](https://github.com/jmbuhr/nvim-config) 36 | and then pick the relevant parts from the 37 | [`lua/plugins/quarto.lua`](https://github.com/jmbuhr/nvim-config/blob/main/lua/plugins/quarto.lua) file 38 | to integrate it into your own existing configuration. 39 | 40 | Plugins and their configuration to look out for in either of those files are: 41 | 42 | ```lua 43 | { 44 | 'quarto-dev/quarto-nvim', 45 | 'jmbuhr/otter.nvim', 46 | 'hrsh7th/nvim-cmp', 47 | 'neovim/nvim-lspconfig', 48 | 'nvim-treesitter/nvim-treesitter' 49 | } 50 | ``` 51 | 52 | Quarto-nvim requires [Neovim stable version](https://github.com/neovim/neovim/releases/tag/stable) >= `v0.10.0`. 53 | 54 | ## Usage 55 | 56 | ### Configure 57 | 58 | You can pass a lua table with options to the setup function or via `lazy.nvim`s `opts` keyword. 59 | It will be merged with the default options, which are shown below in the example. 60 | If you want to use the defaults, simply call `setup` without arguments or with an empty table. 61 | 62 | ```lua 63 | require('quarto').setup{ 64 | debug = false, 65 | closePreviewOnExit = true, 66 | lspFeatures = { 67 | enabled = true, 68 | chunks = "curly", 69 | languages = { "r", "python", "julia", "bash", "html" }, 70 | diagnostics = { 71 | enabled = true, 72 | triggers = { "BufWritePost" }, 73 | }, 74 | completion = { 75 | enabled = true, 76 | }, 77 | }, 78 | codeRunner = { 79 | enabled = true, 80 | default_method = "slime", -- "molten", "slime", "iron" or 81 | ft_runners = {}, -- filetype to runner, ie. `{ python = "molten" }`. 82 | -- Takes precedence over `default_method` 83 | never_run = { 'yaml' }, -- filetypes which are never sent to a code runner 84 | }, 85 | } 86 | ``` 87 | 88 | ### Preview 89 | 90 | Use the command 91 | 92 | ```vim 93 | QuartoPreview 94 | ``` 95 | 96 | or access the function from lua, e.g. to create a keybinding: 97 | 98 | ```lua 99 | local quarto = require('quarto') 100 | quarto.setup() 101 | vim.keymap.set('n', 'qp', quarto.quartoPreview, { silent = true, noremap = true }) 102 | ``` 103 | 104 | Then use the keyboard shortcut to open `quarto preview` for the current file or project in the active working directory in the neovim integrated terminal in a new tab. 105 | 106 | Note: While you can use `QuartoPreview` without configuring the plugin via `quarto.setup`, 107 | other features strictly require it. 108 | 109 | ## Language support 110 | 111 | `quarto-nvim` automatically activates `otter.nvim` for quarto files if language features are enabled. 112 | 113 | ### Demo 114 | 115 | https://user-images.githubusercontent.com/17450586/209436101-4dd560f4-c876-4dbc-a0f4-b3a2cbff0748.mp4 116 | 117 | ### Usage 118 | 119 | You can open the hover documentation for R, python and julia code chunks with `K`, got-to-definition with `gd` etc. 120 | and get autocompletion via the lsp source for your completion plugin. 121 | 122 | A list of currently available language server requests can be found in the [otter.nvim documentation](https://github.com/jmbuhr/otter.nvim?tab=readme-ov-file#lsp-methods-currently-implemented). 123 | 124 | ### R diagnostics configuration 125 | 126 | To make diagnostics work with R you have to configure the linter a bit, since the language 127 | buffers in the background separate code with blank links, which we want to ignore. 128 | Otherwise you get a lot more diagnostics than you probably want. 129 | Add file `.lintr` to your home folder and fill it with: 130 | 131 | ``` 132 | linters: linters_with_defaults( 133 | trailing_blank_lines_linter = NULL, 134 | trailing_whitespace_linter = NULL 135 | ) 136 | ``` 137 | 138 | You can now also enable other lsp features, such as the show hover function 139 | and shortcut, independent of showing diagnostics by enabling lsp features 140 | but not enabling diagnostics. 141 | 142 | ### Other Edge Cases 143 | 144 | Other languages might have similar issues (e.g. I see a lot of warnings about whitespace when activating diagnostics with `lua`). 145 | If you come across them and have a fix, I will be very happy about a pull request! 146 | Or, what might ultimately be the cleaner way of documenting language specific issues, an entry in the [wiki](https://github.com/quarto-dev/quarto-nvim/wiki). 147 | 148 | ## Running Code 149 | 150 | Quarto-nvim doesn't run code for you, instead, it will interface with existing code running 151 | plugins and tell them what to run. There are currently three such code running plugins that quarto 152 | will work with: 153 | 154 | 1. [molten-nvim](https://github.com/benlubas/molten-nvim) - a code runner that supports the jupyter 155 | kernel, renders output below each code cell, and optionally renders images in the terminal. 156 | 2. [vim-slime](https://github.com/jpalardy/vim-slime) - a general purpose code runner with support 157 | for sending code to integrated nvim terminals, tmux panes, and many others. 158 | 3. [iron.nvim](https://github.com/Vigemus/iron.nvim) - general purpose code runner and library for 159 | within-neovim REPL interaction in splits or floating windows. 160 | 161 | We recommend picking a code runner, setting it up based on its respective README and then coming back 162 | to this point to learn how Quarto will augment that code runner. 163 | 164 | This plugin enables easily sending code cells to your code runner. 165 | There are two different ways to do this: 166 | commands, covered below; and lua functions, covered right here. 167 | _By default these functions will only run cells that are the same language as the current cell._ 168 | 169 | Quarto exposes code running functions through to runner module: `require('quarto.runner')`. 170 | Those 171 | functions are: 172 | 173 | - `run_cell()` - runs the current cell 174 | - `run_above(multi_lang)` - runs all the cells above the current one, **and** the current one, in order 175 | - `run_below(multi_lang)` - runs all the cells below the current one, **and** the current one, in order 176 | - `run_all(multi_lang)` - runs all the cells in the document 177 | - `run_line(multi_lang)` - runs the line of code at your cursor 178 | - `run_range()` - run code cells inside (touched by) the current visual range 179 | 180 | Each function that takes the optional `multi_lang` argument will run cells of all languages when 181 | called with the value `true`, and will only run cells that match the language of the current cell 182 | otherwise. As a result, just calling `run_all()` will run all cells that match the language of the 183 | current cell. 184 | 185 | Here are some example run mappings: 186 | 187 | ```lua 188 | local runner = require("quarto.runner") 189 | vim.keymap.set("n", "rc", runner.run_cell, { desc = "run cell", silent = true }) 190 | vim.keymap.set("n", "ra", runner.run_above, { desc = "run cell and above", silent = true }) 191 | vim.keymap.set("n", "rA", runner.run_all, { desc = "run all cells", silent = true }) 192 | vim.keymap.set("n", "rl", runner.run_line, { desc = "run line", silent = true }) 193 | vim.keymap.set("v", "r", runner.run_range, { desc = "run visual range", silent = true }) 194 | vim.keymap.set("n", "RA", function() 195 | runner.run_all(true) 196 | end, { desc = "run all cells of all languages", silent = true }) 197 | ``` 198 | 199 | 200 | ## Available Commands 201 | 202 | ```vim 203 | QuartoPreview 204 | QuartoClosePreview 205 | QuartoHelp <..> 206 | QuartoActivate 207 | QuartoDiagnostics 208 | QuartoSend 209 | QuartoSendAbove 210 | QuartoSendBelow 211 | QuartoSendAll 212 | QuartoSendLine 213 | ``` 214 | 215 | ## Recommended Plugins 216 | 217 | Quarto works great with a number of plugins in the neovim ecosystem. 218 | You can find my (@jmbuhr) personal (and thus up-to-date) configuration for use with Quarto, R and python here: 219 | 220 | 221 | 222 | But remember, the best config is always your own. 223 | -------------------------------------------------------------------------------- /doc/quarto.txt: -------------------------------------------------------------------------------- 1 | *quarto.txt* For Neovim >= 0.8.0 Last change: 2025 August 23 2 | 3 | ============================================================================== 4 | Table of Contents *quarto-table-of-contents* 5 | 6 | 1. quarto-nvim |quarto-quarto-nvim| 7 | - Walkthrough |quarto-quarto-nvim-walkthrough| 8 | - Setup |quarto-quarto-nvim-setup| 9 | - Usage |quarto-quarto-nvim-usage| 10 | - Language support |quarto-quarto-nvim-language-support| 11 | - Running Code |quarto-quarto-nvim-running-code| 12 | - Available Commands |quarto-quarto-nvim-available-commands| 13 | - Recommended Plugins |quarto-quarto-nvim-recommended-plugins| 14 | 2. Links |quarto-links| 15 | 16 | ============================================================================== 17 | 1. quarto-nvim *quarto-quarto-nvim* 18 | 19 | Quarto-nvim provides tools for working on Quarto 20 | manuscripts in Neovim. You can get started with Quarto here 21 | . 22 | 23 | 24 | WALKTHROUGH *quarto-quarto-nvim-walkthrough* 25 | 26 | The get started section 27 | also comes with a video version to walk you through. The playlist is extended 28 | as more features are added, so join us for a "Coffee with Quarto and Neovim" 29 | 30 | 31 | 32 | 33 | SETUP *quarto-quarto-nvim-setup* 34 | 35 | Youcan install `quarto-nvim` from GitHub with your favourite Neovim plugin 36 | manager like lazy.nvim , packer.nvim 37 | or VimPlug 38 | . 39 | 40 | Example `lazy.nvim` plugin specification: 41 | 42 | >lua 43 | -- plugins/quarto.lua 44 | return { 45 | { 46 | "quarto-dev/quarto-nvim", 47 | dependencies = { 48 | "jmbuhr/otter.nvim", 49 | "nvim-treesitter/nvim-treesitter", 50 | }, 51 | }, 52 | } 53 | < 54 | 55 | Because Quarto provides a lot of functionality through integration with 56 | existing plugins, you may want to experiment with my config 57 | and then pick the relevant parts from 58 | the `lua/plugins/quarto.lua` 59 | file 60 | to integrate it into your own existing configuration. 61 | 62 | Plugins and their configuration to look out for in either of those files are: 63 | 64 | >lua 65 | { 66 | 'quarto-dev/quarto-nvim', 67 | 'jmbuhr/otter.nvim', 68 | 'hrsh7th/nvim-cmp', 69 | 'neovim/nvim-lspconfig', 70 | 'nvim-treesitter/nvim-treesitter' 71 | } 72 | < 73 | 74 | Quarto-nvim requires Neovim stable version 75 | >= `v0.10.0`. 76 | 77 | 78 | USAGE *quarto-quarto-nvim-usage* 79 | 80 | 81 | CONFIGURE ~ 82 | 83 | You can pass a lua table with options to the setup function or via `lazy.nvim`s 84 | `opts` keyword. It will be merged with the default options, which are shown 85 | below in the example. If you want to use the defaults, simply call `setup` 86 | without arguments or with an empty table. 87 | 88 | >lua 89 | require('quarto').setup{ 90 | debug = false, 91 | closePreviewOnExit = true, 92 | lspFeatures = { 93 | enabled = true, 94 | chunks = "curly", 95 | languages = { "r", "python", "julia", "bash", "html" }, 96 | diagnostics = { 97 | enabled = true, 98 | triggers = { "BufWritePost" }, 99 | }, 100 | completion = { 101 | enabled = true, 102 | }, 103 | }, 104 | codeRunner = { 105 | enabled = true, 106 | default_method = "slime", -- "molten", "slime", "iron" or 107 | ft_runners = {}, -- filetype to runner, ie. `{ python = "molten" }`. 108 | -- Takes precedence over `default_method` 109 | never_run = { 'yaml' }, -- filetypes which are never sent to a code runner 110 | }, 111 | } 112 | < 113 | 114 | 115 | PREVIEW ~ 116 | 117 | Use the command 118 | 119 | >vim 120 | QuartoPreview 121 | < 122 | 123 | or access the function from lua, e.g. to create a keybinding: 124 | 125 | >lua 126 | local quarto = require('quarto') 127 | quarto.setup() 128 | vim.keymap.set('n', 'qp', quarto.quartoPreview, { silent = true, noremap = true }) 129 | < 130 | 131 | Then use the keyboard shortcut to open `quarto preview` for the current file or 132 | project in the active working directory in the neovim integrated terminal in a 133 | new tab. 134 | 135 | Note: While you can use `QuartoPreview` without configuring the plugin via 136 | `quarto.setup`, other features strictly require it. 137 | 138 | 139 | LANGUAGE SUPPORT *quarto-quarto-nvim-language-support* 140 | 141 | `quarto-nvim` automatically activates `otter.nvim` for quarto files if language 142 | features are enabled. 143 | 144 | 145 | DEMO ~ 146 | 147 | 148 | https://user-images.githubusercontent.com/17450586/209436101-4dd560f4-c876-4dbc-a0f4-b3a2cbff0748.mp4 149 | 150 | 151 | USAGE ~ 152 | 153 | You can open the hover documentation for R, python and julia code chunks with 154 | `K`, got-to-definition with `gd` etc. and get autocompletion via the lsp source 155 | for your completion plugin. 156 | 157 | A list of currently available language server requests can be found in the 158 | otter.nvim documentation 159 | . 160 | 161 | 162 | R DIAGNOSTICS CONFIGURATION ~ 163 | 164 | To make diagnostics work with R you have to configure the linter a bit, since 165 | the language buffers in the background separate code with blank links, which we 166 | want to ignore. Otherwise you get a lot more diagnostics than you probably 167 | want. Add file `.lintr` to your home folder and fill it with: 168 | 169 | > 170 | linters: linters_with_defaults( 171 | trailing_blank_lines_linter = NULL, 172 | trailing_whitespace_linter = NULL 173 | ) 174 | < 175 | 176 | You can now also enable other lsp features, such as the show hover function and 177 | shortcut, independent of showing diagnostics by enabling lsp features but not 178 | enabling diagnostics. 179 | 180 | 181 | OTHER EDGE CASES ~ 182 | 183 | Other languages might have similar issues (e.g. I see a lot of warnings about 184 | whitespace when activating diagnostics with `lua`). If you come across them and 185 | have a fix, I will be very happy about a pull request! Or, what might 186 | ultimately be the cleaner way of documenting language specific issues, an entry 187 | in the wiki . 188 | 189 | 190 | RUNNING CODE *quarto-quarto-nvim-running-code* 191 | 192 | Quarto-nvim doesn’t run code for you, instead, it will interface with 193 | existing code running plugins and tell them what to run. There are currently 194 | three such code running plugins that quarto will work with: 195 | 196 | 1. molten-nvim - a code runner that supports the jupyter 197 | kernel, renders output below each code cell, and optionally renders images in the terminal. 198 | 2. vim-slime - a general purpose code runner with support 199 | for sending code to integrated nvim terminals, tmux panes, and many others. 200 | 3. iron.nvim - general purpose code runner and library for 201 | within-neovim REPL interaction in splits or floating windows. 202 | 203 | We recommend picking a code runner, setting it up based on its respective 204 | README and then coming back to this point to learn how Quarto will augment that 205 | code runner. 206 | 207 | This plugin enables easily sending code cells to your code runner. There are 208 | two different ways to do this: commands, covered below; and lua functions, 209 | covered right here. _By default these functions will only run cells that are 210 | the same language as the current cell._ 211 | 212 | Quarto exposes code running functions through to runner module: 213 | `require('quarto.runner')`. Those functions are: 214 | 215 | - `run_cell()` - runs the current cell 216 | - `run_above(multi_lang)` - runs all the cells above the current one, **and** the current one, in order 217 | - `run_below(multi_lang)` - runs all the cells below the current one, **and** the current one, in order 218 | - `run_all(multi_lang)` - runs all the cells in the document 219 | - `run_line(multi_lang)` - runs the line of code at your cursor 220 | - `run_range()` - run code cells inside (touched by) the current visual range 221 | 222 | Each function that takes the optional `multi_lang` argument will run cells of 223 | all languages when called with the value `true`, and will only run cells that 224 | match the language of the current cell otherwise. As a result, just calling 225 | `run_all()` will run all cells that match the language of the current cell. 226 | 227 | Here are some example run mappings: 228 | 229 | >lua 230 | local runner = require("quarto.runner") 231 | vim.keymap.set("n", "rc", runner.run_cell, { desc = "run cell", silent = true }) 232 | vim.keymap.set("n", "ra", runner.run_above, { desc = "run cell and above", silent = true }) 233 | vim.keymap.set("n", "rA", runner.run_all, { desc = "run all cells", silent = true }) 234 | vim.keymap.set("n", "rl", runner.run_line, { desc = "run line", silent = true }) 235 | vim.keymap.set("v", "r", runner.run_range, { desc = "run visual range", silent = true }) 236 | vim.keymap.set("n", "RA", function() 237 | runner.run_all(true) 238 | end, { desc = "run all cells of all languages", silent = true }) 239 | < 240 | 241 | 242 | AVAILABLE COMMANDS *quarto-quarto-nvim-available-commands* 243 | 244 | >vim 245 | QuartoPreview 246 | QuartoClosePreview 247 | QuartoHelp <..> 248 | QuartoActivate 249 | QuartoDiagnostics 250 | QuartoSend 251 | QuartoSendAbove 252 | QuartoSendBelow 253 | QuartoSendAll 254 | QuartoSendLine 255 | < 256 | 257 | 258 | RECOMMENDED PLUGINS *quarto-quarto-nvim-recommended-plugins* 259 | 260 | Quarto works great with a number of plugins in the neovim ecosystem. You can 261 | find my (@jmbuhr) personal (and thus up-to-date) configuration for use with 262 | Quarto, R and python here: 263 | 264 | 265 | 266 | But remember, the best config is always your own. 267 | 268 | ============================================================================== 269 | 2. Links *quarto-links* 270 | 271 | 1. *@jmbuhr*: 272 | 273 | Generated by panvimdoc 274 | 275 | vim:tw=78:ts=8:noet:ft=help:norl: 276 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [1.2.5](https://github.com/quarto-dev/quarto-nvim/compare/v1.2.4...v1.2.5) (2025-08-23) 4 | 5 | 6 | ### Bug Fixes 7 | 8 | * pass `root_dir` to preview for project type ([#190](https://github.com/quarto-dev/quarto-nvim/issues/190)) ([e6b34cd](https://github.com/quarto-dev/quarto-nvim/commit/e6b34cdc56cad6e83f4d8c7737f4444f78ad29a2)) 9 | 10 | ## [1.2.4](https://github.com/quarto-dev/quarto-nvim/compare/v1.2.3...v1.2.4) (2025-04-10) 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * add `search_ancestors` function to util ([#178](https://github.com/quarto-dev/quarto-nvim/issues/178)) ([6cf5fce](https://github.com/quarto-dev/quarto-nvim/commit/6cf5fce22c993ef06b954ad00fe722d9d162549e)) 16 | 17 | ## [1.2.3](https://github.com/quarto-dev/quarto-nvim/compare/v1.2.2...v1.2.3) (2025-04-08) 18 | 19 | 20 | ### Bug Fixes 21 | 22 | * copy lspconfig util functions to remove dependency. fixes [#175](https://github.com/quarto-dev/quarto-nvim/issues/175) ([06a9082](https://github.com/quarto-dev/quarto-nvim/commit/06a9082ade62f103663e14ce721dc7d372294895)) 23 | 24 | ## [1.2.2](https://github.com/quarto-dev/quarto-nvim/compare/v1.2.1...v1.2.2) (2025-03-24) 25 | 26 | 27 | ### Bug Fixes 28 | 29 | * make usercommands require quarto only on use ([74df376](https://github.com/quarto-dev/quarto-nvim/commit/74df3767fa6c0d00e152e317c983c495f2cade6a)) 30 | 31 | ## [1.2.1](https://github.com/quarto-dev/quarto-nvim/compare/v1.2.0...v1.2.1) (2025-02-03) 32 | 33 | 34 | ### Bug Fixes 35 | 36 | * do not activate in special buffers (fixes [#164](https://github.com/quarto-dev/quarto-nvim/issues/164)) ([#165](https://github.com/quarto-dev/quarto-nvim/issues/165)) ([bb710f4](https://github.com/quarto-dev/quarto-nvim/commit/bb710f433ea6b0acaeb672d26f6641bd5723e0ef)) 37 | 38 | ## [1.2.0](https://github.com/quarto-dev/quarto-nvim/compare/v1.1.0...v1.2.0) (2024-12-22) 39 | 40 | 41 | ### Features 42 | 43 | * detect editor: render-on-save: false ([#159](https://github.com/quarto-dev/quarto-nvim/issues/159)) ([ca2d1c7](https://github.com/quarto-dev/quarto-nvim/commit/ca2d1c790e0d8a46096ed428255c65eb8eff7c30)) 44 | * QuartoPreviewNoWatch and QuartoUpdatePreview commands ([#157](https://github.com/quarto-dev/quarto-nvim/issues/157)) ([601c32b](https://github.com/quarto-dev/quarto-nvim/commit/601c32b89cc77f0f44b9208a1347c3294a64338c)) 45 | 46 | ## [1.1.0](https://github.com/quarto-dev/quarto-nvim/compare/v1.0.2...v1.1.0) (2024-12-20) 47 | 48 | 49 | ### Features 50 | 51 | * support Iron.nvim and user functions for sending code to REPL ([#109](https://github.com/quarto-dev/quarto-nvim/issues/109)) ([eb98585](https://github.com/quarto-dev/quarto-nvim/commit/eb98585b13f8d5ddc21c9a9413b7b3b848099653)) 52 | 53 | 54 | ### Bug Fixes 55 | 56 | * Replace term:// with termopen() to resolve wildcard expansion error ([#153](https://github.com/quarto-dev/quarto-nvim/issues/153)) ([f810c62](https://github.com/quarto-dev/quarto-nvim/commit/f810c62842e9a8e9e2d44fa8a47196e7d36d79c4)) 57 | 58 | ## [1.0.2](https://github.com/quarto-dev/quarto-nvim/compare/v1.0.1...v1.0.2) (2024-10-09) 59 | 60 | 61 | ### Bug Fixes 62 | 63 | * register markdown grammar for quarto and rmd until our own grammar is ready (https://github.com/jmbuhr/tree-sitter-pandoc-markdown) refs https://github.com/jmbuhr/otter.nvim/issues/179 ([1d10f13](https://github.com/quarto-dev/quarto-nvim/commit/1d10f135937613ca71a84975fe75b5b76a35065e)) 64 | 65 | ## [1.0.1](https://github.com/quarto-dev/quarto-nvim/compare/v1.0.0...v1.0.1) (2024-06-30) 66 | 67 | 68 | ### Bug Fixes 69 | 70 | * use renamed table from otter.nvim. fixes [#137](https://github.com/quarto-dev/quarto-nvim/issues/137). ([c873128](https://github.com/quarto-dev/quarto-nvim/commit/c87312856469c8aaa30b3097a1280eea58ab1902)) 71 | 72 | ## [1.0.0](https://github.com/quarto-dev/quarto-nvim/compare/v0.18.2...v1.0.0) (2024-06-29) 73 | 74 | 75 | ### ⚠ BREAKING CHANGES 76 | 77 | * remove the need for custom otter.nvim keybindings ([#135](https://github.com/quarto-dev/quarto-nvim/issues/135)) 78 | 79 | ### Features 80 | 81 | * remove the need for custom otter.nvim keybindings ([#135](https://github.com/quarto-dev/quarto-nvim/issues/135)) ([1665721](https://github.com/quarto-dev/quarto-nvim/commit/1665721f7ba16671f519f3cd87382bc28258af04)) 82 | 83 | ## [0.18.2](https://github.com/quarto-dev/quarto-nvim/compare/v0.18.1...v0.18.2) (2024-02-19) 84 | 85 | 86 | ### Bug Fixes 87 | 88 | * only require otter when activating ([5336b86](https://github.com/quarto-dev/quarto-nvim/commit/5336b86dc3d0517075debe8906671daeeab9f5ed)) 89 | 90 | ## [0.18.1](https://github.com/quarto-dev/quarto-nvim/compare/v0.18.0...v0.18.1) (2024-02-19) 91 | 92 | 93 | ### Bug Fixes 94 | 95 | * make disabling keybindings easier. refs [#116](https://github.com/quarto-dev/quarto-nvim/issues/116) ([1b15dd1](https://github.com/quarto-dev/quarto-nvim/commit/1b15dd175b974cb8c83b022f68cc07c02c9c465b)) 96 | * QuartoHover user function ([#113](https://github.com/quarto-dev/quarto-nvim/issues/113)) ([a4760c0](https://github.com/quarto-dev/quarto-nvim/commit/a4760c0b275972bc8ef577f7521771d17cb0cd17)) 97 | 98 | ## [0.18.0](https://github.com/quarto-dev/quarto-nvim/compare/v0.17.0...v0.18.0) (2023-11-29) 99 | 100 | 101 | ### Features 102 | 103 | * configurable code runner with molten-nvim integration ([#99](https://github.com/quarto-dev/quarto-nvim/issues/99)) ([eacd8ff](https://github.com/quarto-dev/quarto-nvim/commit/eacd8ff211923c1b11a021ae6291bc34d9472948)) 104 | 105 | 106 | ### Bug Fixes 107 | 108 | * get current bufnr in ftplugin instead of relying on 0 ([af34813](https://github.com/quarto-dev/quarto-nvim/commit/af3481378ba7b664499fd1bbb9ae5fd6612d04fc)) 109 | * readme configruation ([#103](https://github.com/quarto-dev/quarto-nvim/issues/103)) ([1fe0f16](https://github.com/quarto-dev/quarto-nvim/commit/1fe0f163c42efdddb4d8b9ac8ac0e55eb20ff17c)) 110 | * user commands giving bad args to run funcs ([#101](https://github.com/quarto-dev/quarto-nvim/issues/101)) ([68ac6c0](https://github.com/quarto-dev/quarto-nvim/commit/68ac6c0500bcd0f3e978bd16c7d56e93ee8928da)) 111 | 112 | ## [0.17.0](https://github.com/quarto-dev/quarto-nvim/compare/v0.16.0...v0.17.0) (2023-09-08) 113 | 114 | 115 | ### Features 116 | 117 | * pass arguments to quarto preview ([#88](https://github.com/quarto-dev/quarto-nvim/issues/88)) ([bad6f70](https://github.com/quarto-dev/quarto-nvim/commit/bad6f70269bcaf063513782c085aa2295ed3af25)) 118 | 119 | ## [0.16.0](https://github.com/quarto-dev/quarto-nvim/compare/v0.15.1...v0.16.0) (2023-08-27) 120 | 121 | 122 | ### Features 123 | 124 | * format current code chunk ([4ba80ce](https://github.com/quarto-dev/quarto-nvim/commit/4ba80ce2ba73811228df88f4aa5294f528912417)) 125 | 126 | ## [0.15.1](https://github.com/quarto-dev/quarto-nvim/compare/v0.15.0...v0.15.1) (2023-07-17) 127 | 128 | 129 | ### Bug Fixes 130 | 131 | * clarify autcommand usage for setting keymaps ([d076de2](https://github.com/quarto-dev/quarto-nvim/commit/d076de2a43ad6b856b64da29dfa89cc1f6fba3f1)) 132 | 133 | ## [0.15.0](https://github.com/quarto-dev/quarto-nvim/compare/v0.14.1...v0.15.0) (2023-07-16) 134 | 135 | 136 | ### Features 137 | 138 | * trigger release! ([fa9cc99](https://github.com/quarto-dev/quarto-nvim/commit/fa9cc994c4d76fa1e72778f5857cb8038451499f)) 139 | 140 | ## [0.14.1](https://github.com/quarto-dev/quarto-nvim/compare/v0.14.0...v0.14.1) (2023-07-05) 141 | 142 | 143 | ### Bug Fixes 144 | 145 | * fix [#76](https://github.com/quarto-dev/quarto-nvim/issues/76). Stick with vim.loop until stable release of nvim 0.10 (then it's vim.uv) ([aa42597](https://github.com/quarto-dev/quarto-nvim/commit/aa4259729e8b0878be8e06e98f601569059284b9)) 146 | 147 | ## [0.14.0](https://github.com/quarto-dev/quarto-nvim/compare/v0.13.2...v0.14.0) (2023-06-28) 148 | 149 | 150 | ### Features 151 | 152 | * **lsp:** keybindings for ask_type_definition and ask_document_symbol ([2fd6169](https://github.com/quarto-dev/quarto-nvim/commit/2fd616956e65c9073d043eb631e251da6faa3404)) 153 | 154 | 155 | ### Bug Fixes 156 | 157 | * replace deprecated vim.loop with vim.uv ([1f043c8](https://github.com/quarto-dev/quarto-nvim/commit/1f043c81ec9e75046a6e1f315561e6333656d5c7)) 158 | 159 | 160 | ### Performance Improvements 161 | 162 | * put quarto init in ftplugin instea of autocommand ([1f2ccef](https://github.com/quarto-dev/quarto-nvim/commit/1f2ccefc22d3cad64bd10782b1670d8b6835cf1e)) 163 | 164 | ## [0.13.2](https://github.com/quarto-dev/quarto-nvim/compare/v0.13.1...v0.13.2) (2023-06-21) 165 | 166 | 167 | ### Performance Improvements 168 | 169 | * don't register quarto->markdown treesitter ft ([9f02823](https://github.com/quarto-dev/quarto-nvim/commit/9f02823d7b38b2e9c578bac085c430f14b74df3b)) 170 | 171 | ## [0.13.1](https://github.com/quarto-dev/quarto-nvim/compare/v0.13.0...v0.13.1) (2023-06-06) 172 | 173 | 174 | ### Bug Fixes 175 | 176 | * slime-sending. ([795133e](https://github.com/quarto-dev/quarto-nvim/commit/795133eaa3ee9995674d81f8718623f5aaf03bca)) 177 | 178 | ## [0.13.0](https://github.com/quarto-dev/quarto-nvim/compare/v0.12.0...v0.13.0) (2023-06-06) 179 | 180 | 181 | ### Features 182 | 183 | * enable completion for html also if only activating `curly` chunks ([27ac79f](https://github.com/quarto-dev/quarto-nvim/commit/27ac79fb897cee6452d05711241ff6318cd25a9d)) 184 | 185 | ## [0.12.0](https://github.com/quarto-dev/quarto-nvim/compare/v0.11.0...v0.12.0) (2023-06-03) 186 | 187 | 188 | ### Features 189 | 190 | * add html support through otter.nvim update (injections) ([f9fbdab](https://github.com/quarto-dev/quarto-nvim/commit/f9fbdab68d4af02733e1b983f494ecd56e8f1050)) 191 | 192 | ## [0.11.0](https://github.com/quarto-dev/quarto-nvim/compare/v0.10.1...v0.11.0) (2023-05-26) 193 | 194 | 195 | ### Features 196 | 197 | * trigger release for otter.nvim rename and references ([2c013ae](https://github.com/quarto-dev/quarto-nvim/commit/2c013ae7f05554a78d9cb956ec73444513f336bf)) 198 | 199 | ## [0.10.1](https://github.com/quarto-dev/quarto-nvim/compare/v0.10.0...v0.10.1) (2023-05-20) 200 | 201 | 202 | ### Bug Fixes 203 | 204 | * remove duplicate queries (closes [#58](https://github.com/quarto-dev/quarto-nvim/issues/58)) ([9306dcc](https://github.com/quarto-dev/quarto-nvim/commit/9306dcc7272655e46712a26a15c65d801b8b7b2e)) 205 | 206 | ## [0.10.0](https://github.com/quarto-dev/quarto-nvim/compare/v0.9.0...v0.10.0) (2023-05-06) 207 | 208 | 209 | ### Features 210 | 211 | * QuartoSendBelow and QuartoSendRange ([7226eee](https://github.com/quarto-dev/quarto-nvim/commit/7226eeecd42182c0051a0959983e15e9a4e0b939)) 212 | 213 | 214 | ### Performance Improvements 215 | 216 | * **diagnostics:** initialize diagnostic namespaces once and save ids ([f60eb6a](https://github.com/quarto-dev/quarto-nvim/commit/f60eb6a877c17af8c92490e96148172463b68627)) 217 | 218 | ## [0.9.0](https://github.com/quarto-dev/quarto-nvim/compare/v0.8.1...v0.9.0) (2023-04-20) 219 | 220 | 221 | ### Features 222 | 223 | * QuartoSendAbove and QuartoSendAll commands to send code to ([cb2bb7d](https://github.com/quarto-dev/quarto-nvim/commit/cb2bb7d47f02b5abfa60fa80d24fe4b4b9120d92)) 224 | 225 | ## [0.8.1](https://github.com/quarto-dev/quarto-nvim/compare/v0.8.0...v0.8.1) (2023-04-20) 226 | 227 | 228 | ### Bug Fixes 229 | 230 | * quarto preview in Windows/PowerShell ([#53](https://github.com/quarto-dev/quarto-nvim/issues/53)) ([8980f73](https://github.com/quarto-dev/quarto-nvim/commit/8980f739045867b2c59612e380ecb32dbf3df803)) 231 | 232 | ## [0.8.0](https://github.com/quarto-dev/quarto-nvim/compare/v0.7.3...v0.8.0) (2023-04-09) 233 | 234 | 235 | ### Features 236 | 237 | * nvim 0.9.0! ([#50](https://github.com/quarto-dev/quarto-nvim/issues/50)) ([ac9bafe](https://github.com/quarto-dev/quarto-nvim/commit/ac9bafe821aecfa7e3071d5b6e936588e0deff4c)) 238 | 239 | ## [0.7.3](https://github.com/quarto-dev/quarto-nvim/compare/v0.7.2...v0.7.3) (2023-04-04) 240 | 241 | 242 | ### Bug Fixes 243 | 244 | * activate quarto only once per buffer ([9de52c8](https://github.com/quarto-dev/quarto-nvim/commit/9de52c85423fbc218f7324be4af662c32aee3da9)) 245 | * remove debug statement ([a9f9f98](https://github.com/quarto-dev/quarto-nvim/commit/a9f9f98da951ee7146d519ddc624013e6bdcd6aa)) 246 | 247 | ## [0.7.2](https://github.com/quarto-dev/quarto-nvim/compare/v0.7.1...v0.7.2) (2023-03-11) 248 | 249 | 250 | ### Bug Fixes 251 | 252 | * fix package name for release ([96f741c](https://github.com/quarto-dev/quarto-nvim/commit/96f741cd04dd769e9ce1c1aaa913ee6296594a47)) 253 | 254 | ## [0.7.1](https://github.com/quarto-dev/quarto-nvim/compare/v0.7.0...v0.7.1) (2023-03-11) 255 | 256 | 257 | ### Bug Fixes 258 | 259 | * format and trigger release ([9e2adb0](https://github.com/quarto-dev/quarto-nvim/commit/9e2adb0e93e2d3c7ae1ce0471bcd8113faa03521)) 260 | * just use otter.ask_hover ([31ba845](https://github.com/quarto-dev/quarto-nvim/commit/31ba845274e2a1f77dd5ebe2890e182856776a15)) 261 | 262 | ## [0.7.0](https://github.com/quarto-dev/quarto-nvim/compare/v0.6.0...v0.7.0) (2023-02-15) 263 | 264 | 265 | ### Features 266 | 267 | * use quarto filetype instead of markdown ([#26](https://github.com/quarto-dev/quarto-nvim/issues/26)) ([449e877](https://github.com/quarto-dev/quarto-nvim/commit/449e877005d544dc931be36177728482aec49a03)) 268 | 269 | ## [0.6.0](https://github.com/quarto-dev/quarto-nvim/compare/v0.5.3...v0.6.0) (2023-01-25) 270 | 271 | 272 | ### Features 273 | 274 | * add group for qmd open autocmd. ([#27](https://github.com/quarto-dev/quarto-nvim/issues/27)) ([467da36](https://github.com/quarto-dev/quarto-nvim/commit/467da365225d9606e074cdb8eb7cb3e520ecc270)) 275 | --------------------------------------------------------------------------------