Skip to content

Commit

Permalink
Autocomplete (#18)
Browse files Browse the repository at this point in the history
* add cmp completion source!
* autocompletion now works with one langauge at at time, but not two
  - most likely a name clash?
* update diagnostics less often by default
  • Loading branch information
jmbuhr authored Dec 19, 2022
1 parent cd2d5aa commit 68838dd
Show file tree
Hide file tree
Showing 5 changed files with 313 additions and 70 deletions.
6 changes: 5 additions & 1 deletion examples/example.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ def hello():
print("Hello")
```

```{python}
import numpy as np
```

This is how we call it:

```{python}
Expand All @@ -22,7 +26,7 @@ hello()
And this function is not found because we have a typo:

```{python}
helo()
hello()
```

Now, we use the function in the next code chunk
Expand Down
200 changes: 136 additions & 64 deletions lua/quarto/init.lua
Original file line number Diff line number Diff line change
@@ -1,82 +1,31 @@
local M = {}
local api = vim.api
local util = require "lspconfig.util"
local source = require'quarto.source'
local tools = require'quarto.tools'
local lines = tools.lines
local spaces = tools.spaces
local api = vim.api

local defaultConfig = {
M.defaultConfig = {
debug = false,
closePreviewOnExit = true,
lspFeatures = {
enabled = false,
languages = { 'r', 'python', 'julia' },
diagnostics = {
enabled = true,
triggers = { "BufEnter", "InsertLeave", "TextChanged" }
},
cmpSource = {
enabled = true,
},
},
keymap = {
hover = 'K',
}
}

M.config = defaultConfig


function M.quartoPreview()
-- find root directory / check if it is a project
local buffer_path = api.nvim_buf_get_name(0)
local root_dir = util.root_pattern("_quarto.yml")(buffer_path)
local cmd
local mode
if root_dir then
mode = "project"
cmd = 'quarto preview'
else
mode = "file"
cmd = 'quarto preview ' .. buffer_path
end

local quarto_extensions = { ".qmd", ".Rmd", ".ipynb", ".md" }
local file_extension = buffer_path:match("^.+(%..+)$")
if mode == "file" and not file_extension then
vim.notify("Not in a file. exiting.")
return
end
if mode == "file" and not tools.contains(quarto_extensions, file_extension) then
vim.notify("Not a quarto file, ends in " .. file_extension .. " exiting.")
return
end

-- run command in embedded terminal
-- in a new tab and go back to the buffer
vim.cmd('tabedit term://' .. cmd)
local quartoOutputBuf = vim.api.nvim_get_current_buf()
vim.cmd('tabprevious')
api.nvim_buf_set_var(0, 'quartoOutputBuf', quartoOutputBuf)


-- close preview terminal on exit of the quarto buffer
if M.config.closePreviewOnExit then
api.nvim_create_autocmd({ "QuitPre", "WinClosed" }, {
buffer = api.nvim_get_current_buf(),
group = api.nvim_create_augroup("quartoPreview", {}),
callback = function(_, _)
if api.nvim_buf_is_loaded(quartoOutputBuf) then
api.nvim_buf_delete(quartoOutputBuf, { force = true })
end
end
})
end
end

function M.quartoClosePreview()
local success, quartoOutputBuf = pcall(api.nvim_buf_get_var, 0, 'quartoOutputBuf')
if not success then return end
if api.nvim_buf_is_loaded(quartoOutputBuf) then
api.nvim_buf_delete(quartoOutputBuf, { force = true })
end
end

local function get_language_content(bufnr)
-- get and parse AST
Expand Down Expand Up @@ -124,7 +73,7 @@ local function get_language_content(bufnr)
return results
end

local function update_language_buffers(qmd_bufnr)
M.updateLanguageBuffers = function(qmd_bufnr)
local language_content = get_language_content(qmd_bufnr)
local bufnrs = {}
for _, lang in ipairs(M.config.lspFeatures.languages) do
Expand Down Expand Up @@ -161,9 +110,118 @@ local function update_language_buffers(qmd_bufnr)
return bufnrs
end

---Registered client and source mapping.
M.cmp_client_source_map = {}

---Setup cmp-nvim-lsp source.
M.cmp_setup_source = function(qmdbufnr, bufnr)
local callback = function()
M.cmp_on_insert_enter(qmdbufnr, bufnr)
end
vim.api.nvim_create_autocmd('InsertEnter', {
buffer = qmdbufnr,
group = vim.api.nvim_create_augroup('cmp_quarto'..bufnr, { clear = true }),
callback = callback
})
end

---Refresh sources on InsertEnter.
-- adds a source for the hidden language buffer bufnr
M.cmp_on_insert_enter = function(qmdbufnr, bufnr)
local cmp = require('cmp')
local allowed_clients = {}

-- register all active clients.
for _, client in ipairs(vim.lsp.get_active_clients({bufnr = bufnr})) do
allowed_clients[client.id] = client
if not M.cmp_client_source_map[client.id] then
local s = source.new(client, qmdbufnr, bufnr, M.updateLanguageBuffers)
if s:is_available() then
P('register source for ' .. s.client.name)
M.cmp_client_source_map[client.id] = cmp.register_source('quarto', s)
end
end
end

-- register all buffer clients (early register before activation)
for _, client in ipairs(vim.lsp.buf_get_clients(0)) do
allowed_clients[client.id] = client
if not M.cmp_client_source_map[client.id] then
local s = source.new(client, qmdbufnr, bufnr, M.updateLanguageBuffers)
if s:is_available() then
M.cmp_client_source_map[client.id] = cmp.register_source('quarto', s)
end
end
end

-- unregister stopped/detached clients.
for client_id, source_id in pairs(M.cmp_client_source_map) do
if not allowed_clients[client_id] or allowed_clients[client_id]:is_stopped() then
cmp.unregister_source(source_id)
M.cmp_client_source_map[client_id] = nil
end
end
end


function M.quartoPreview()
-- find root directory / check if it is a project
local buffer_path = api.nvim_buf_get_name(0)
local root_dir = util.root_pattern("_quarto.yml")(buffer_path)
local cmd
local mode
if root_dir then
mode = "project"
cmd = 'quarto preview'
else
mode = "file"
cmd = 'quarto preview ' .. buffer_path
end

local quarto_extensions = { ".qmd", ".Rmd", ".ipynb", ".md" }
local file_extension = buffer_path:match("^.+(%..+)$")
if mode == "file" and not file_extension then
vim.notify("Not in a file. exiting.")
return
end
if mode == "file" and not tools.contains(quarto_extensions, file_extension) then
vim.notify("Not a quarto file, ends in " .. file_extension .. " exiting.")
return
end

-- run command in embedded terminal
-- in a new tab and go back to the buffer
vim.cmd('tabedit term://' .. cmd)
local quartoOutputBuf = vim.api.nvim_get_current_buf()
vim.cmd('tabprevious')
api.nvim_buf_set_var(0, 'quartoOutputBuf', quartoOutputBuf)


-- close preview terminal on exit of the quarto buffer
if M.config.closePreviewOnExit then
api.nvim_create_autocmd({ "QuitPre", "WinClosed" }, {
buffer = api.nvim_get_current_buf(),
group = api.nvim_create_augroup("quartoPreview", {}),
callback = function(_, _)
if api.nvim_buf_is_loaded(quartoOutputBuf) then
api.nvim_buf_delete(quartoOutputBuf, { force = true })
end
end
})
end
end

function M.quartoClosePreview()
local success, quartoOutputBuf = pcall(api.nvim_buf_get_var, 0, 'quartoOutputBuf')
if not success then return end
if api.nvim_buf_is_loaded(quartoOutputBuf) then
api.nvim_buf_delete(quartoOutputBuf, { force = true })
end
end

M.activateLspFeatures = function()
local qmdbufnr = api.nvim_get_current_buf()
local bufnrs = update_language_buffers(qmdbufnr)
local bufnrs = M.updateLanguageBuffers(qmdbufnr)

-- auto-close language files on qmd file close
api.nvim_create_autocmd({ "QuitPre", "WinClosed" }, {
Expand All @@ -186,17 +244,31 @@ M.activateLspFeatures = function()
M.enableDiagnostics()
end

if M.config.lspFeatures.cmpSource.enabled then
for _,bufnr in ipairs(bufnrs) do
M.cmp_setup_source(qmdbufnr, bufnr)
end

api.nvim_create_autocmd({ "TextChangedI" }, {
buffer = 0,
group = api.nvim_create_augroup("quartoCmp", { clear = false }),
callback = function(_, _)
local bufnrs = M.updateLanguageBuffers(0)
end
})
end

local key = M.config.keymap.hover
vim.api.nvim_set_keymap('n', key, ":lua require'quarto'.quartoHover()<cr>", { silent = true })
end

M.enableDiagnostics = function()
-- update diagnostics on changes
api.nvim_create_autocmd({ "CursorHold", "TextChanged" }, {
api.nvim_create_autocmd(M.config.lspFeatures.diagnostics.triggers, {
buffer = 0,
group = api.nvim_create_augroup("quartoLSPDiagnositcs", { clear = false }),
callback = function(_, _)
local bufnrs = update_language_buffers(0)
local bufnrs = M.updateLanguageBuffers(0)
for _, bufnr in ipairs(bufnrs) do
local diag = vim.diagnostic.get(bufnr)
local ns = api.nvim_create_namespace('quarto-lang-' .. bufnr)
Expand All @@ -209,7 +281,7 @@ end

M.quartoHover = function()
local qmdbufnr = api.nvim_get_current_buf()
local bufnrs = update_language_buffers(qmdbufnr)
local bufnrs = M.updateLanguageBuffers(qmdbufnr)
for _, bufnr in ipairs(bufnrs) do
local uri = vim.uri_from_bufnr(bufnr)
local position_params = vim.lsp.util.make_position_params()
Expand Down Expand Up @@ -243,7 +315,7 @@ end

-- setup
M.setup = function(opt)
M.config = vim.tbl_deep_extend('force', defaultConfig, opt or {})
M.config = vim.tbl_deep_extend('force', M.defaultConfig, opt or {})
end

M.debug = function()
Expand Down
Loading

0 comments on commit 68838dd

Please sign in to comment.