From 24a5ec2752755940da62aec56c77cc5bd1c73891 Mon Sep 17 00:00:00 2001 From: Iain Lane Date: Wed, 9 Apr 2025 07:07:56 +0100 Subject: [PATCH 1/9] chore: add .stylua.toml and run an initial format with it This makes formatting consistent across different users. --- .stylua.toml | 2 ++ lua/nvim-jsonnet.lua | 58 ++++++++++++++++++-------------------------- 2 files changed, 26 insertions(+), 34 deletions(-) create mode 100644 .stylua.toml diff --git a/.stylua.toml b/.stylua.toml new file mode 100644 index 0000000..53d1469 --- /dev/null +++ b/.stylua.toml @@ -0,0 +1,2 @@ +indent_type = "Spaces" +quote_style = "AutoPreferSingle" diff --git a/lua/nvim-jsonnet.lua b/lua/nvim-jsonnet.lua index df853a5..a40805f 100644 --- a/lua/nvim-jsonnet.lua +++ b/lua/nvim-jsonnet.lua @@ -31,48 +31,38 @@ M.setup = function(options) end end - vim.api.nvim_create_user_command( - 'JsonnetPrintConfig', - function() - print(vim.inspect(M.options)) - end, {}) + vim.api.nvim_create_user_command('JsonnetPrintConfig', function() + print(vim.inspect(M.options)) + end, {}) - vim.api.nvim_create_user_command( - 'JsonnetEval', - function(opts) - utils.RunCommand(M.options.jsonnet_bin, M.options.jsonnet_args, 'json', opts) - end, - { nargs = '?' }) + vim.api.nvim_create_user_command('JsonnetEval', function(opts) + utils.RunCommand(M.options.jsonnet_bin, M.options.jsonnet_args, 'json', opts) + end, { nargs = '?' }) - vim.api.nvim_create_user_command( - 'JsonnetEvalString', - function(opts) - utils.RunCommand(M.options.jsonnet_string_bin, M.options.jsonnet_string_args, '', opts) - end, - { nargs = '?' }) + vim.api.nvim_create_user_command('JsonnetEvalString', function(opts) + utils.RunCommand(M.options.jsonnet_string_bin, M.options.jsonnet_string_args, '', opts) + end, { nargs = '?' }) - vim.api.nvim_create_autocmd( - 'FileType', - { - pattern = { 'jsonnet' }, - callback = function() - vim.keymap.set('n', 'j', 'JsonnetEval') - vim.keymap.set('n', 'k', 'JsonnetEvalString') - vim.keymap.set('n', 'l', ':<\',\'>!jsonnetfmt -') - vim.opt_local.foldlevelstart = 1 - end, - }) + vim.api.nvim_create_autocmd('FileType', { + pattern = { 'jsonnet' }, + callback = function() + vim.keymap.set('n', 'j', 'JsonnetEval') + vim.keymap.set('n', 'k', 'JsonnetEvalString') + vim.keymap.set('n', 'l', ":<','>!jsonnetfmt -") + vim.opt_local.foldlevelstart = 1 + end, + }) local hasLspconfig, lspconfig = pcall(require, 'lspconfig') if M.options.load_lsp_config and hasLspconfig then - lspconfig.jsonnet_ls.setup { + lspconfig.jsonnet_ls.setup({ capabilities = M.options.capabilities, settings = { formatting = { - UseImplicitPlus = stringtoboolean[os.getenv('JSONNET_IMPLICIT_PLUS')] or false - } - } - } + UseImplicitPlus = stringtoboolean[os.getenv('JSONNET_IMPLICIT_PLUS')] or false, + }, + }, + }) end local hasDap, dap = pcall(require, 'dap') @@ -88,7 +78,7 @@ M.setup = function(options) request = 'launch', name = 'debug', program = '${file}', - } + }, } end end From de2dec53c71245f39cea66a683fb0d9599a1ea83 Mon Sep 17 00:00:00 2001 From: Iain Lane Date: Wed, 9 Apr 2025 08:34:10 +0100 Subject: [PATCH 2/9] feat: customisable key mappings Add a `key_prefix` string, a `keys` table and a `setup_mappings` boolean to the options to allow key mappings to be customised or switched off. User-provided mappings are merged with defaults, which allows individual fields to be updated. --- lua/nvim-jsonnet.lua | 55 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/lua/nvim-jsonnet.lua b/lua/nvim-jsonnet.lua index a40805f..a01d566 100644 --- a/lua/nvim-jsonnet.lua +++ b/lua/nvim-jsonnet.lua @@ -17,9 +17,59 @@ local defaults = { load_dap_config = false, jsonnet_debugger_bin = 'jsonnet-debugger', jsonnet_debugger_args = { '-s', '-d', '-J', 'vendor', '-J', 'lib' }, + + -- A prefix prepended to all key mappings + key_prefix = '', + + -- Keymap configuration. Each key can be individually overridden. Each binding + -- will have `key_prefix` prepended. + keys = { + eval = { + key = 'j', + desc = 'Evaluate Jsonnet file', + mode = 'n', + cmd = 'JsonnetEval', + enabled = true, + }, + eval_string = { + key = 'k', + desc = 'Evaluate Jsonnet file as string', + mode = 'n', + cmd = 'JsonnetEvalString', + enabled = true, + }, + format = { + key = 'l', + desc = 'Format Jsonnet file', + mode = 'n', + cmd = '!jsonnetfmt %', + enabled = true, + }, + }, + + -- Set to false to disable all default key mappings + setup_mappings = true, } +local function apply_mappings() + if not M.options.setup_mappings then + return + end + + for _, mapping_config in pairs(M.options.keys) do + if mapping_config.enabled then + vim.keymap.set( + mapping_config.mode, + M.options.key_prefix .. mapping_config.key, + mapping_config.cmd, + { desc = mapping_config.desc, silent = true, noremap = true } + ) + end + end +end + M.setup = function(options) + -- Merge user options with defaults M.options = vim.tbl_deep_extend('force', {}, defaults, options or {}) if M.options.use_tanka_if_possible then @@ -46,9 +96,8 @@ M.setup = function(options) vim.api.nvim_create_autocmd('FileType', { pattern = { 'jsonnet' }, callback = function() - vim.keymap.set('n', 'j', 'JsonnetEval') - vim.keymap.set('n', 'k', 'JsonnetEvalString') - vim.keymap.set('n', 'l', ":<','>!jsonnetfmt -") + apply_mappings() + vim.opt_local.foldlevelstart = 1 end, }) From 109983ace733a04d3ba2aadc4fbd7233bad5910a Mon Sep 17 00:00:00 2001 From: Iain Lane Date: Wed, 9 Apr 2025 08:36:39 +0100 Subject: [PATCH 3/9] chore: Add command descriptions, move to local functions Just a small tidy up which removes some code from the setup function. Adding descriptions to the commands means that various things like `cmp` or `which-key` can pick them up and show them to the user. --- lua/nvim-jsonnet.lua | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/lua/nvim-jsonnet.lua b/lua/nvim-jsonnet.lua index a01d566..184188d 100644 --- a/lua/nvim-jsonnet.lua +++ b/lua/nvim-jsonnet.lua @@ -68,6 +68,19 @@ local function apply_mappings() end end +local function eval_jsonnet(opts) + utils.RunCommand(M.options.jsonnet_bin, M.options.jsonnet_args, 'json', opts) +end + +local function eval_jsonnet_string(opts) + utils.RunCommand(M.options.jsonnet_string_bin, M.options.jsonnet_string_args, '', opts) +end + +local function format_jsonnet() + vim.cmd('!jsonnetfmt %') +end + +-- Setup function to initialize the plugin M.setup = function(options) -- Merge user options with defaults M.options = vim.tbl_deep_extend('force', {}, defaults, options or {}) @@ -81,17 +94,25 @@ M.setup = function(options) end end + M.eval_jsonnet = eval_jsonnet + M.eval_jsonnet_string = eval_jsonnet_string + M.format_jsonnet = format_jsonnet + vim.api.nvim_create_user_command('JsonnetPrintConfig', function() print(vim.inspect(M.options)) - end, {}) + end, { desc = 'Print Jsonnet plugin configuration' }) vim.api.nvim_create_user_command('JsonnetEval', function(opts) - utils.RunCommand(M.options.jsonnet_bin, M.options.jsonnet_args, 'json', opts) - end, { nargs = '?' }) + M.eval_jsonnet(opts) + end, { nargs = '?', desc = 'Evaluate Jsonnet file' }) vim.api.nvim_create_user_command('JsonnetEvalString', function(opts) - utils.RunCommand(M.options.jsonnet_string_bin, M.options.jsonnet_string_args, '', opts) - end, { nargs = '?' }) + M.eval_jsonnet_string(opts) + end, { nargs = '?', desc = 'Evaluate Jsonnet file as string' }) + + vim.api.nvim_create_user_command('JsonnetFormat', function() + M.format_jsonnet() + end, { desc = 'Format Jsonnet file' }) vim.api.nvim_create_autocmd('FileType', { pattern = { 'jsonnet' }, From 0004de0076cba81cdd7e96d1b0ad0bc3d00ccf61 Mon Sep 17 00:00:00 2001 From: Iain Lane Date: Wed, 9 Apr 2025 07:41:15 +0100 Subject: [PATCH 4/9] fix!: fix module structure to be importable The current setup is giving ``` Failed to run `config` for nvim-jsonnet ...local/share/nvim/lazy/lazy.nvim/lua/lazy/core/loader.lua:387: module 'jsonnet' not found: no field package.preload['jsonnet'] cache_loader: module 'jsonnet' not found cache_loader_lib: module 'jsonnet' not found no file './jsonnet.lua' no file '/opt/homebrew/share/luajit-2.1/jsonnet.lua' no file '/usr/local/share/lua/5.1/jsonnet.lua' no file '/usr/local/share/lua/5.1/jsonnet/init.lua' no file '/opt/homebrew/share/lua/5.1/jsonnet.lua' no file '/opt/homebrew/share/lua/5.1/jsonnet/init.lua' no file './jsonnet.so' no file '/usr/local/lib/lua/5.1/jsonnet.so' no file '/opt/homebrew/lib/lua/5.1/jsonnet.so' no file '/usr/local/lib/lua/5.1/loadall.so' - ~/.config/nvim/lua/config/lazy.lua:30 - ~/.config/nvim/init.lua:1 ``` when we try to load the plugin using `lazy.nvim`. This is because the plugin name was `nvim-jsonnet` but we weren't under a `lua` directory with that name. Moving the files to `lua/nvim-jsonnet` and providing an `init.lua` file fixes this. --- lua/{nvim-jsonnet.lua => nvim-jsonnet/init.lua} | 2 +- lua/{jsonnet => nvim-jsonnet}/utils.lua | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename lua/{nvim-jsonnet.lua => nvim-jsonnet/init.lua} (99%) rename lua/{jsonnet => nvim-jsonnet}/utils.lua (100%) diff --git a/lua/nvim-jsonnet.lua b/lua/nvim-jsonnet/init.lua similarity index 99% rename from lua/nvim-jsonnet.lua rename to lua/nvim-jsonnet/init.lua index 184188d..ab58a6c 100644 --- a/lua/nvim-jsonnet.lua +++ b/lua/nvim-jsonnet/init.lua @@ -1,4 +1,4 @@ -local utils = require('jsonnet.utils') +local utils = require('nvim-jsonnet.utils') local stringtoboolean = { ['true'] = true, ['false'] = false } local M = {} diff --git a/lua/jsonnet/utils.lua b/lua/nvim-jsonnet/utils.lua similarity index 100% rename from lua/jsonnet/utils.lua rename to lua/nvim-jsonnet/utils.lua From ff76fad19b22b3b2a5c079cd571f71790766d7ac Mon Sep 17 00:00:00 2001 From: Iain Lane Date: Wed, 9 Apr 2025 08:19:07 +0100 Subject: [PATCH 5/9] feat: run setup function lazily Instead of doing all of the work at initialisation time in the plugin's `setup` function, add a callback which will be invoked when we enter a Jsonnet buffer. To prevent it running multiple times, we set a flag `do_setup` and skip if it's already been run. --- lua/nvim-jsonnet/init.lua | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/lua/nvim-jsonnet/init.lua b/lua/nvim-jsonnet/init.lua index ab58a6c..e99413c 100644 --- a/lua/nvim-jsonnet/init.lua +++ b/lua/nvim-jsonnet/init.lua @@ -1,7 +1,9 @@ local utils = require('nvim-jsonnet.utils') local stringtoboolean = { ['true'] = true, ['false'] = false } -local M = {} +local M = { + did_setup = false, +} local defaults = { jsonnet_bin = os.getenv('JSONNET_BIN') or 'jsonnet', @@ -80,8 +82,13 @@ local function format_jsonnet() vim.cmd('!jsonnetfmt %') end --- Setup function to initialize the plugin -M.setup = function(options) +local function do_setup(options) + if M.did_setup then + return + end + + M.did_setup = true + -- Merge user options with defaults M.options = vim.tbl_deep_extend('force', {}, defaults, options or {}) @@ -116,11 +123,7 @@ M.setup = function(options) vim.api.nvim_create_autocmd('FileType', { pattern = { 'jsonnet' }, - callback = function() - apply_mappings() - - vim.opt_local.foldlevelstart = 1 - end, + callback = apply_mappings, }) local hasLspconfig, lspconfig = pcall(require, 'lspconfig') @@ -153,4 +156,18 @@ M.setup = function(options) end end +M.setup = function(options) + vim.api.nvim_create_autocmd('FileType', { + pattern = { 'jsonnet' }, + callback = function(_) + do_setup(options) + + apply_mappings() + + -- Set folding options + vim.opt_local.foldlevelstart = 1 + end, + }) +end + return M From 4fb3624b406bc1b69b5c53c9bae2df84edf23428 Mon Sep 17 00:00:00 2001 From: Iain Lane Date: Wed, 9 Apr 2025 08:21:57 +0100 Subject: [PATCH 6/9] feat: make commands buffer-local This means that we don't expose the `Jsonnet*` commands in other buffers, as they are not relevant there. --- lua/nvim-jsonnet/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/nvim-jsonnet/init.lua b/lua/nvim-jsonnet/init.lua index e99413c..ea94e53 100644 --- a/lua/nvim-jsonnet/init.lua +++ b/lua/nvim-jsonnet/init.lua @@ -64,7 +64,7 @@ local function apply_mappings() mapping_config.mode, M.options.key_prefix .. mapping_config.key, mapping_config.cmd, - { desc = mapping_config.desc, silent = true, noremap = true } + { desc = mapping_config.desc, silent = true, noremap = true, buffer = true } ) end end From 312cdb222aef7dc35ed3e96ab558d9b87c2ceb96 Mon Sep 17 00:00:00 2001 From: Iain Lane Date: Wed, 9 Apr 2025 08:23:25 +0100 Subject: [PATCH 7/9] fix!: don't set `foldlevelstart` This is a user configuration option, not something a plugin should set. --- lua/nvim-jsonnet/init.lua | 3 --- 1 file changed, 3 deletions(-) diff --git a/lua/nvim-jsonnet/init.lua b/lua/nvim-jsonnet/init.lua index ea94e53..36ca907 100644 --- a/lua/nvim-jsonnet/init.lua +++ b/lua/nvim-jsonnet/init.lua @@ -163,9 +163,6 @@ M.setup = function(options) do_setup(options) apply_mappings() - - -- Set folding options - vim.opt_local.foldlevelstart = 1 end, }) end From aa4369499305f278f590a19eedc90676188802fe Mon Sep 17 00:00:00 2001 From: Iain Lane Date: Sun, 13 Apr 2025 16:43:47 +0100 Subject: [PATCH 8/9] feat: support different layouts This commit integrates the layout support from [`CopilotChat.nvim`][CopilotChat], which some modifications. There's a new `window` table in the configuration which allows the layout to be customised. The job/execution handling is updated a bit to align with this too. Now we keep a reference to a single buffer around, and track whether it's visible or hidden etc. [CopilotChat]: https://github.com/CopilotC-Nvim/CopilotChat.nvim --- README.md | 43 ++++ lua/nvim-jsonnet/buffer.lua | 481 ++++++++++++++++++++++++++++++++++++ lua/nvim-jsonnet/config.lua | 144 +++++++++++ lua/nvim-jsonnet/init.lua | 192 ++++++++------ lua/nvim-jsonnet/job.lua | 89 +++++++ lua/nvim-jsonnet/utils.lua | 52 +--- screenshots/edgy.png | Bin 0 -> 28644 bytes screenshots/float.png | Bin 0 -> 33538 bytes 8 files changed, 871 insertions(+), 130 deletions(-) create mode 100644 lua/nvim-jsonnet/buffer.lua create mode 100644 lua/nvim-jsonnet/config.lua create mode 100644 lua/nvim-jsonnet/job.lua create mode 100644 screenshots/edgy.png create mode 100644 screenshots/float.png diff --git a/README.md b/README.md index 18dba20..26b076b 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,49 @@ vim.wo.foldexpr = 'v:lua.vim.treesitter.foldexpr()' vim.wo.foldlevel = 1000 ``` +### Window layouts + +Various window layouts are available. See [./lua/nvim-jsonnet/config.lua] for +full details of all the configuration options and the defaults. Set just what +you need: unchanged values will be taken from the defaults. + +Use a floating window sized to 40% of the screen width: + +```lua +window = { + layout = "float", + width = 0.4 +} +``` + +![Floating window](./screenshots/float.png) + +Integrate with [`edgy.nvim`][edgy]'s sidebar: + +```lua +// This is an example `lazy.nvim` configuration +{ + "folke/edgy.nvim", + + optional = true, + + opts = function(_, opts) + opts.right = opts.right or {} + + table.insert(opts.right, { + ft = "jsonnet-output", + title = "Jsonnet", + + size = { width = 50 }, + }) + end, +}, +``` + +![Edgy sidebar](./screenshots/edgy.png) + +[edgy]: https://github.com/folke/edgy.nvim + ### null-ls/cbfmt For formatting code blocks inside Markdown you can use null-ls with `cbfmt`. diff --git a/lua/nvim-jsonnet/buffer.lua b/lua/nvim-jsonnet/buffer.lua new file mode 100644 index 0000000..c3768de --- /dev/null +++ b/lua/nvim-jsonnet/buffer.lua @@ -0,0 +1,481 @@ +---@class nvim-jsonnet.Buffer +---@field augroup number? The ID of the autocmd group +---@field buffer_number number? Buffer number +---@field config nvim-jsonnet.Config|{} Configuration options +---@field layout string? Current layout mode +---@field name string Buffer name +---@field on_buf_create fun(buf: nvim-jsonnet.Buffer)? Function to call when buffer is created +---@field private help string Help message to display +---@field private job_id number? ID of the running job +---@field source_buffer_number number? Source buffer number +---@field source_window_number number? Source window number +local Buffer = {} +Buffer.__index = Buffer + +--- Create a new buffer +---@param name string The buffer name +---@param help string Help message to display +---@param on_buf_create fun(buf: nvim-jsonnet.Buffer)? Function to call when buffer is created +---@return nvim-jsonnet.Buffer +function Buffer.new(name, help, on_buf_create) + local self = setmetatable({}, Buffer) + self.name = name + self.help = help + self.on_buf_create = on_buf_create + self.layout = nil + self.config = {} + self.job_id = nil + + return self +end + +--- Returns whether the buffer window is visible and its window number (if it is). +---@return number|nil The window number if visible, nil otherwise +function Buffer:visible() + if not self:buf_valid() then + return nil + end + + -- Check if our buffer is visible in any window + for _, win in ipairs(vim.api.nvim_list_wins()) do + if vim.api.nvim_win_is_valid(win) and vim.api.nvim_win_get_buf(win) == self.buffer_number then + return win + end + end + + return nil +end +--- +--- Returns whether the buffer window is focused. +---@return boolean +function Buffer:focused() + local window_number = self:visible() + + return window_number ~= nil and vim.api.nvim_get_current_win() == window_number +end + +--- Check if the buffer is valid +---@return boolean +function Buffer:buf_valid() + return self.buffer_number ~= nil + and vim.api.nvim_buf_is_valid(self.buffer_number) + and vim.api.nvim_buf_is_loaded(self.buffer_number) +end + +--- Validate the buffer +function Buffer:validate() + if self:buf_valid() then + return + end + + self.buffer_number = self:create() + if self.on_buf_create ~= nil then + self:on_buf_create() + end +end + +--- Create the buffer +---@return number Buffer number +function Buffer:create() + local buffer_number = vim.api.nvim_create_buf(false, true) + vim.bo[buffer_number].modifiable = false + vim.api.nvim_buf_set_name(buffer_number, 'jsonnet-output://' .. self.name) + + -- Track buffer deletion, so we can abort any running jobs + vim.api.nvim_create_autocmd('BufDelete', { + buffer = buffer_number, + callback = function() + self:cancel_running_job() + self.buffer_number = nil + end, + once = true, + }) + + return buffer_number +end + +--- Setup autocmds to track source buffer visibility +function Buffer:setup_source_tracking() + if not self.source_buffer_number then + return + end + + if self.augroup ~= nil then + pcall(function() + vim.api.nvim_del_augroup_by_id(self.augroup) + end) + end + + self.augroup = vim.api.nvim_create_augroup('JsonnetAutoclose_' .. self.buffer_number, { clear = true }) + + vim.api.nvim_create_autocmd({ 'BufWinLeave', 'BufWinEnter', 'WinClosed', 'WinEnter', 'WinLeave' }, { + group = self.augroup, + callback = function() + -- Check if source buffer is visible + local source_visible = false + for _, win in ipairs(vim.api.nvim_list_wins()) do + if vim.api.nvim_win_is_valid(win) and vim.api.nvim_win_get_buf(win) == self.source_buffer_number then + source_visible = true + break + end + end + + if source_visible then + return + end + + -- If source is not visible in any window, close the output + self:close() + end, + }) +end + +--- Clean up any autocmds we created +function Buffer:cleanup_source_tracking() + if self.augroup == nil then + return + end + + pcall(function() + vim.api.nvim_del_augroup_by_id(self.augroup) + end) + self.augroup = nil +end + +--- Setup floating window behavior for this buffer +---@param width number The window width +---@param height number The window height +---@param window nvim-jsonnet.Config.Window Window configuration +function Buffer:setup_float(width, height, window) + local win_opts = { + style = 'minimal', + width = width, + height = height, + zindex = window.zindex, + relative = window.relative, + border = window.border, + title = window.title, + row = window.row or math.floor((vim.o.lines - height) / 2), + col = window.col or math.floor((vim.o.columns - width) / 2), + footer = self.help, + } + + local window_number = vim.api.nvim_open_win(self.buffer_number, false, win_opts) + vim.api.nvim_set_option_value('winblend', 10, { win = window_number }) + vim.api.nvim_set_option_value('winhl', 'Normal:FloatWindow', { win = window_number }) + + local float_augroup = vim.api.nvim_create_augroup('FloatWindowBehaviour_' .. self.buffer_number, { clear = true }) + + -- Close the window on any cursor movement outside the float + vim.api.nvim_create_autocmd({ 'CursorMoved', 'CursorMovedI', 'WinClosed' }, { + group = float_augroup, + callback = function() + -- Only close if we're not in the floating window + if self:focused() then + return + end + + self:close() + + pcall(vim.api.nvim_del_augroup_by_id, float_augroup) + end, + }) + + -- Or when the float is explicitly closed + vim.api.nvim_create_autocmd('BufLeave', { + group = float_augroup, + buffer = self.buffer_number, + callback = function() + self:close() + + pcall(vim.api.nvim_del_augroup_by_id, float_augroup) + end, + }) +end + +--- Setup vertical split window for this buffer +---@param width number The window width +function Buffer:setup_vertical(width) + local orig = vim.api.nvim_get_current_win() + local cmd = 'vsplit' + if width ~= 0 then + cmd = width .. cmd + end + if vim.api.nvim_get_option_value('splitright', {}) then + cmd = 'botright ' .. cmd + else + cmd = 'topleft ' .. cmd + end + vim.cmd(cmd) + + local window_number = vim.api.nvim_get_current_win() + vim.api.nvim_win_set_buf(window_number, self.buffer_number) + + vim.api.nvim_set_current_win(orig) +end + +--- Setup horizontal split window for this buffer +---@param height number The window height +function Buffer:setup_horizontal(height) + local orig = vim.api.nvim_get_current_win() + local cmd = 'split' + if height ~= 0 then + cmd = height .. cmd + end + if vim.api.nvim_get_option_value('splitbelow', {}) then + cmd = 'botright ' .. cmd + else + cmd = 'topleft ' .. cmd + end + vim.cmd(cmd) + + local window_number = vim.api.nvim_get_current_win() + vim.api.nvim_win_set_buf(window_number, self.buffer_number) + + vim.api.nvim_set_current_win(orig) +end + +--- Setup common window options +function Buffer:setup_window_options() + local window_number = self:visible() + + if not window_number or not vim.api.nvim_win_is_valid(window_number) then + return + end + + vim.wo[window_number].wrap = true + vim.wo[window_number].linebreak = true + vim.wo[window_number].cursorline = true + vim.wo[window_number].conceallevel = 2 + vim.wo[window_number].foldlevel = 99 +end + +--- Open the buffer window. +---@param window nvim-jsonnet.Config.Window Config options for the output window +function Buffer:open(window) + self:validate() + + local layout = window.layout + if type(layout) == 'function' then + layout = layout() + end + + local width = window.width > 1 and window.width or math.floor(vim.o.columns * window.width) + local height = window.height > 1 and window.height or math.floor(vim.o.lines * window.height) + + -- If layout changed or window isn't visible, we'll close and reopen + if self.layout ~= layout or not self:visible() then + self:close() + end + + self.layout = layout + + if self:visible() then + return + end + + if layout == 'float' then + self:setup_float(width, height, window) + elseif layout == 'vertical' then + self:setup_vertical(width) + elseif layout == 'horizontal' then + self:setup_horizontal(height) + elseif layout == 'replace' then + local current_window_number = vim.api.nvim_get_current_win() + local current_buffer_number = vim.api.nvim_win_get_buf(current_window_number) + + -- Save where we came from + self.source_window_number = current_window_number + self.source_buffer_number = current_buffer_number + + -- Replace the buffer in the current window + vim.api.nvim_win_set_buf(current_window_number, self.buffer_number) + end + + if layout ~= 'replace' then + self:setup_source_tracking() + end + + self:setup_window_options() +end + +--- Close the buffer window. +function Buffer:close() + local window_number = self:visible() + + if not window_number then + return + end + + if self:focused() then + local mode = vim.fn.mode():lower() + if mode:find('v') then + vim.cmd([[execute "normal! \"]]) + elseif mode ~= 'n' then + vim.cmd('stopinsert') + end + end + + if self.layout == 'replace' then + self:restore() + return + end + + -- Check if window is still valid before trying to close it + if window_number and vim.api.nvim_win_is_valid(window_number) then + vim.api.nvim_win_close(window_number, true) + end + + self:cleanup_source_tracking() +end + +--- Toggle the buffer window. +---@param window nvim-jsonnet.Config.Window +function Buffer:toggle(window) + if self:visible() then + self:close() + else + self:open(window) + end +end + +--- Focus the buffer window. +function Buffer:focus() + local window_number = self:visible() + + if not window_number then + return + end + + vim.api.nvim_set_current_win(window_number) +end + +--- Restore the original buffer +function Buffer:restore() + if not self.source_window_number or not self.source_buffer_number then + return + end + + if not vim.api.nvim_win_is_valid(self.source_window_number) then + return + end + + vim.api.nvim_win_set_buf(self.source_window_number, self.source_buffer_number) + vim.api.nvim_win_set_hl_ns(self.source_window_number, 0) + + -- Manually trigger BufEnter event as nvim_win_set_buf does not trigger it + vim.schedule(function() + vim.cmd(string.format('doautocmd BufEnter %s', self.source_buffer_number)) + end) +end + +--- Set the buffer content +---@param content string +---@param filetype string +function Buffer:set_content(content, filetype) + self:validate() + + vim.bo[self.buffer_number].modifiable = true + vim.api.nvim_buf_set_lines(self.buffer_number, 0, -1, false, vim.split(content, '\n')) + vim.bo[self.buffer_number].modifiable = false + + vim.bo[self.buffer_number].filetype = filetype +end + +--- Run a command and show the output in this buffer +---@param cmd string The command to run +---@param args string[] The arguments to pass to the command +---@param cwd string? The working directory +---@param filetype string The filetype to set for the buffer +---@param window nvim-jsonnet.Config.Window The window configuration, if a new buffer needs to be created +---@param return_focus boolean Whether to return focus to the source window after running the command +function Buffer:run_command(cmd, args, cwd, filetype, window, return_focus) + self:validate() + + self:cancel_running_job() + + local output_lines = {} + local error_lines = {} + + self.job_id = vim.fn.jobstart({ cmd, unpack(args) }, { + stdout_buffered = false, + stderr_buffered = false, + cwd = cwd, + + on_stdout = function(_, data) + if not data or #data == 0 then + return + end + + for _, line in ipairs(data) do + if line ~= '' then + table.insert(output_lines, line) + end + end + end, + + on_stderr = function(_, data) + if not data or #data == 0 then + return + end + + for _, line in ipairs(data) do + if line ~= '' then + table.insert(error_lines, line) + end + end + end, + + on_exit = function(_, exit_code) + self.job_id = nil + + if not self:buf_valid() then + return + end + + if exit_code ~= 0 then + -- Format error message for notification + local error_header = 'Command `' + .. cmd + .. ' ' + .. table.concat(args, ' ') + .. '` failed with exit code: ' + .. exit_code + local error_msg = error_header .. '\n' .. table.concat(error_lines, '\n') + + -- Display error notification + vim.notify(error_msg, vim.log.levels.ERROR) + + return + end + + self:set_content(table.concat(output_lines, '\n'), filetype) + + -- Force close and reopen to ensure proper window state + self:close() + self:open(window) + + if return_focus and self.source_window_number and vim.api.nvim_win_is_valid(self.source_window_number) then + vim.api.nvim_set_current_win(self.source_window_number) + end + end, + }) + + if self.job_id <= 0 then + vim.notify('Failed to start command: ' .. cmd .. ' ' .. table.concat(args, ' '), vim.log.levels.ERROR) + self:close() + self.job_id = nil + end +end + +--- Cancel the running job if there is one +function Buffer:cancel_running_job() + if not self.job_id then + return + end + + vim.fn.jobstop(self.job_id) + self.job_id = nil +end + +return Buffer diff --git a/lua/nvim-jsonnet/config.lua b/lua/nvim-jsonnet/config.lua new file mode 100644 index 0000000..6f52f0a --- /dev/null +++ b/lua/nvim-jsonnet/config.lua @@ -0,0 +1,144 @@ +local utils = require('nvim-jsonnet.utils') + +---@alias nvim-jsonnet.config.Layout 'vertical'|'horizontal'|'float'|'replace' + +---@class nvim-jsonnet.config.KeyMapping +---@field key string The key sequence (e.g., 'j', 'j') +---@field filetype? string|table Filetype(s) for the mapping +---@field desc string Description shown in help/which-key +---@field mode 'n'|'v'|'i'|'x' The mode(s) for the mapping +---@field cmd string|fun(nvim_jsonnet: nvim-jsonnet) The command string or Lua function to execute +---@field enabled boolean Whether the mapping is enabled + +---@class nvim-jsonnet.config.KeyMappingGroup: table + +---@class nvim-jsonnet.config.KeysConfig: nvim-jsonnet.config.KeyMappingGroup +---@field eval? nvim-jsonnet.config.KeyMapping Mapping for evaluation +---@field eval_string? nvim-jsonnet.config.KeyMapping Mapping for string evaluation +---@field format? nvim-jsonnet.config.KeyMapping Mapping for formatting + +---@class nvim-jsonnet.config.OutputKeysConfig: nvim-jsonnet.config.KeyMappingGroup +---@field toggle_output? nvim-jsonnet.config.KeyMapping Mapping for toggling output +---@field close? nvim-jsonnet.config.KeyMapping Mapping for closing output buffer + +---@class nvim-jsonnet.Config.Window +---@field layout nvim-jsonnet.config.Layout|fun():string Layout mode of the output window +---@field width number Fractional width (when <= 1) or absolute columns (when > 1) +---@field height number Fractional height (when <= 1) or absolute rows (when > 1) +---@field relative 'editor'|'win'|'cursor'|'mouse' Position relative to (floating windows only) +---@field border 'none'|'single'|'double'|'rounded'|'solid'|'shadow' Window border style (floating windows only) +---@field row? number Row position of the window, centered by default (floating windows only) +---@field col? number Column position of the window, centered by default (floating windows only) +---@field title? string Title of the output window (floating windows only) +---@field footer? string Footer of the output window (floating windows only) +---@field zindex? number Z-index for floating windows (floating windows only) + +---@class nvim-jsonnet.Config +---@field jsonnet_bin string Path to jsonnet executable +---@field jsonnet_args string[] Arguments for jsonnet command +---@field jsonnet_string_bin string Path to jsonnet executable for string output +---@field jsonnet_string_args string[] Arguments for jsonnet string command +---@field use_tanka_if_possible boolean Whether to use Tanka if available +---@field load_lsp_config boolean Whether to load LSP configuration +---@field capabilities table LSP capabilities +---@field load_dap_config boolean Whether to load DAP configuration +---@field jsonnet_debugger_bin string Path to jsonnet debugger executable +---@field jsonnet_debugger_args string[] Arguments for jsonnet debugger +---@field output_filetype string Filetype for the output buffer +---@field return_focus boolean Whether to return focus to source window after evaluation +---@field show_errors_in_buffer boolean Whether to show errors in the output buffer (true) or as notifications (false) +---@field key_prefix string Prefix for key mappings +---@field keys? nvim-jsonnet.config.KeysConfig Key mapping configuration +---@field output_keys? nvim-jsonnet.config.OutputKeysConfig Key mapping configuration for jsonnet and the output buffer +---@field setup_mappings boolean Whether to set up mappings automatically +---@field window? nvim-jsonnet.Config.Window Window configuration options + +---@type nvim-jsonnet.Config +local config = { + jsonnet_bin = os.getenv('JSONNET_BIN') or 'jsonnet', + jsonnet_args = { '-J', 'vendor', '-J', 'lib' }, + jsonnet_string_bin = os.getenv('JSONNET_BIN') or 'jsonnet', + jsonnet_string_args = { '-S', '-J', 'vendor', '-J', 'lib' }, + use_tanka_if_possible = utils.stringtoboolean[os.getenv('NVIM_JSONNET_USE_TANKA') or 'true'], + + load_lsp_config = false, + capabilities = vim.lsp.protocol.make_client_capabilities(), + + load_dap_config = false, + jsonnet_debugger_bin = 'jsonnet-debugger', + jsonnet_debugger_args = { '-s', '-d', '-J', 'vendor', '-J', 'lib' }, + + output_filetype = 'json', -- Default output filetype for evaluation + return_focus = true, -- Whether to return focus to source window after evaluation + show_errors_in_buffer = false, -- Whether to show errors in buffer (true) or as notifications (false) + + -- A prefix prepended to all key mappings + key_prefix = '', + + keys = { + eval = { + key = 'j', + desc = 'Evaluate Jsonnet file', + mode = 'n', + cmd = 'JsonnetEval', + enabled = true, + }, + eval_string = { + key = 'k', + desc = 'Evaluate Jsonnet file as string', + mode = 'n', + cmd = 'JsonnetEvalString', + enabled = true, + }, + format = { + key = 'l', + desc = 'Format Jsonnet file', + mode = 'n', + cmd = 'JsonnetFormat', + enabled = true, + }, + }, + + -- These keybindings are active in both `jsonnet` files and also the output + -- window + output_keys = { + toggle_output = { + key = 'o', + desc = 'Toggle Jsonnet output buffer', + mode = 'n', + cmd = 'JsonnetToggle', + enabled = true, + }, + close = { + key = 'q', + desc = 'Close Jsonnet output buffer', + mode = 'n', + cmd = function(nvim_jsonnet) + if nvim_jsonnet.output_buffer == nil then + return + end + + nvim_jsonnet.output_buffer:close() + end, + enabled = true, + }, + }, + + setup_mappings = true, + + window = { + layout = 'vertical', -- 'vertical', 'horizontal', 'float', 'replace' + width = 0.5, -- fractional width of parent, or absolute width in columns when > 1 + height = 0.5, -- fractional height of parent, or absolute height in rows when > 1 + -- Options below only apply to floating windows + relative = 'editor', -- 'editor', 'win', 'cursor', 'mouse' + border = 'single', -- 'none', 'single', 'double', 'rounded', 'solid', 'shadow' + -- row = nil, + -- col = nil, + title = 'Jsonnet Output', -- title of output window + -- footer = nil, + zindex = 1, -- determines if window is on top or below other floating windows + }, +} + +return config diff --git a/lua/nvim-jsonnet/init.lua b/lua/nvim-jsonnet/init.lua index 36ca907..5f215b8 100644 --- a/lua/nvim-jsonnet/init.lua +++ b/lua/nvim-jsonnet/init.lua @@ -1,83 +1,103 @@ +local Buffer = require('nvim-jsonnet.buffer') +local job = require('nvim-jsonnet.job') local utils = require('nvim-jsonnet.utils') -local stringtoboolean = { ['true'] = true, ['false'] = false } +--- A custom filetype, mapped to `json`, used by `edgy.nvim` to manage the +--- output buffer. +local filetype = 'jsonnet-output' + +--- @class nvim-jsonnet +--- @field did_setup boolean Indicates if the plugin has been set up +--- @field output_buffer? nvim-jsonnet.Buffer The reusable output buffer for jsonnet evaluation +--- @field options nvim-jsonnet.Config The configuration options for the plugin local M = { did_setup = false, + output_buffer = nil, } -local defaults = { - jsonnet_bin = os.getenv('JSONNET_BIN') or 'jsonnet', - jsonnet_args = { '-J', 'vendor', '-J', 'lib' }, - jsonnet_string_bin = os.getenv('JSONNET_BIN') or 'jsonnet', - jsonnet_string_args = { '-S', '-J', 'vendor', '-J', 'lib' }, - use_tanka_if_possible = stringtoboolean[os.getenv('NVIM_JSONNET_USE_TANKA') or 'true'], - - load_lsp_config = false, - capabilities = vim.lsp.protocol.make_client_capabilities(), - - -- default to false to not break existing installs - load_dap_config = false, - jsonnet_debugger_bin = 'jsonnet-debugger', - jsonnet_debugger_args = { '-s', '-d', '-J', 'vendor', '-J', 'lib' }, - - -- A prefix prepended to all key mappings - key_prefix = '', - - -- Keymap configuration. Each key can be individually overridden. Each binding - -- will have `key_prefix` prepended. - keys = { - eval = { - key = 'j', - desc = 'Evaluate Jsonnet file', - mode = 'n', - cmd = 'JsonnetEval', - enabled = true, - }, - eval_string = { - key = 'k', - desc = 'Evaluate Jsonnet file as string', - mode = 'n', - cmd = 'JsonnetEvalString', - enabled = true, - }, - format = { - key = 'l', - desc = 'Format Jsonnet file', - mode = 'n', - cmd = '!jsonnetfmt %', - enabled = true, - }, - }, - - -- Set to false to disable all default key mappings - setup_mappings = true, -} - -local function apply_mappings() +--- Set key mappings +---@param mappings nvim-jsonnet.config.KeyMappingGroup The mappings to set +local function apply_mappings(mappings) if not M.options.setup_mappings then return end - for _, mapping_config in pairs(M.options.keys) do - if mapping_config.enabled then - vim.keymap.set( - mapping_config.mode, - M.options.key_prefix .. mapping_config.key, - mapping_config.cmd, - { desc = mapping_config.desc, silent = true, noremap = true, buffer = true } - ) + for _, mapping_config in pairs(mappings) do + if not mapping_config.enabled then + goto continue end + + local cmd = type(mapping_config.cmd) == 'function' and function() + mapping_config.cmd(M) + end or mapping_config.cmd + + vim.keymap.set( + mapping_config.mode, + M.options.key_prefix .. mapping_config.key, + cmd, + { desc = mapping_config.desc, silent = true, noremap = true, buffer = true } + ) + + ::continue:: end end -local function eval_jsonnet(opts) - utils.RunCommand(M.options.jsonnet_bin, M.options.jsonnet_args, 'json', opts) +--- Get the current buffer's filepath +--- @return string path, string dir +local function get_buffer_path() + local bufname = vim.api.nvim_buf_get_name(0) + local path = vim.fn.fnamemodify(bufname, ':p') + local dir = vim.fn.fnamemodify(path, ':h') + return path, dir end -local function eval_jsonnet_string(opts) - utils.RunCommand(M.options.jsonnet_string_bin, M.options.jsonnet_string_args, '', opts) +--- Initialise the output buffer if not already created +local function ensure_output_buffer() + if M.output_buffer then + return + end + + local how_to_close = { + 'Perform any movement', + } + + if M.options.setup_mappings and M.options.output_keys.close.enabled then + table.insert(how_to_close, 'press ' .. M.options.key_prefix .. M.options.output_keys.close.key) + end + + local close_message = table.concat(how_to_close, ' or ') .. ' to close this buffer' + + M.output_buffer = Buffer.new('jsonnet-output', close_message, function(buf) + M.output_buffer = buf + end) end +-- Run the given jsonnet command and output to our shared buffer +local function run_jsonnet(jsonnet, args, ft) + ensure_output_buffer() + + -- Get file path and directory + local path, dir = get_buffer_path() + + -- Build command arguments + local args_copy = vim.deepcopy(args) + table.insert(args_copy, path) + + -- Open output buffer and run command + M.output_buffer:run_command(jsonnet, args_copy, dir, ft, M.options.window, M.options.return_focus) +end + +-- Evaluate the current buffer as Jsonnet +local function eval_jsonnet() + run_jsonnet(M.options.jsonnet_bin, M.options.jsonnet_args, filetype) +end + +-- Evaluate the current buffer as Jsonnet string +local function eval_jsonnet_string() + run_jsonnet(M.options.jsonnet_string_bin, M.options.jsonnet_string_args, 'text') +end + +-- Format the current buffer using jsonnetfmt local function format_jsonnet() vim.cmd('!jsonnetfmt %') end @@ -90,12 +110,12 @@ local function do_setup(options) M.did_setup = true -- Merge user options with defaults - M.options = vim.tbl_deep_extend('force', {}, defaults, options or {}) + M.options = vim.tbl_deep_extend('force', {}, require('nvim-jsonnet.config'), options or {}) if M.options.use_tanka_if_possible then -- Use Tanka if `tk tool jpath` works. - local _ = vim.fn.system('tk tool jpath ' .. vim.fn.shellescape(vim.fn.expand('%'))) - if vim.api.nvim_get_vvar('shell_error') == 0 then + local result = job.system({ 'tk', 'tool', 'jpath', vim.fn.expand('%') }) + if result.code == 0 then M.options.jsonnet_bin = 'tk' M.options.jsonnet_args = { 'eval' } end @@ -105,39 +125,40 @@ local function do_setup(options) M.eval_jsonnet_string = eval_jsonnet_string M.format_jsonnet = format_jsonnet + -- Create user commands vim.api.nvim_create_user_command('JsonnetPrintConfig', function() print(vim.inspect(M.options)) end, { desc = 'Print Jsonnet plugin configuration' }) - vim.api.nvim_create_user_command('JsonnetEval', function(opts) - M.eval_jsonnet(opts) - end, { nargs = '?', desc = 'Evaluate Jsonnet file' }) + vim.api.nvim_create_user_command('JsonnetEval', eval_jsonnet, { nargs = '?', desc = 'Evaluate Jsonnet file' }) - vim.api.nvim_create_user_command('JsonnetEvalString', function(opts) - M.eval_jsonnet_string(opts) - end, { nargs = '?', desc = 'Evaluate Jsonnet file as string' }) + vim.api.nvim_create_user_command( + 'JsonnetEvalString', + eval_jsonnet_string, + { nargs = '?', desc = 'Evaluate Jsonnet file as string' } + ) - vim.api.nvim_create_user_command('JsonnetFormat', function() - M.format_jsonnet() - end, { desc = 'Format Jsonnet file' }) + vim.api.nvim_create_user_command('JsonnetFormat', format_jsonnet, { desc = 'Format Jsonnet file' }) - vim.api.nvim_create_autocmd('FileType', { - pattern = { 'jsonnet' }, - callback = apply_mappings, - }) + vim.api.nvim_create_user_command('JsonnetToggle', function() + ensure_output_buffer() + M.output_buffer:toggle(M.options.window) + end, { desc = 'Toggle Jsonnet output buffer' }) + -- Set up LSP if requested local hasLspconfig, lspconfig = pcall(require, 'lspconfig') if M.options.load_lsp_config and hasLspconfig then lspconfig.jsonnet_ls.setup({ capabilities = M.options.capabilities, settings = { formatting = { - UseImplicitPlus = stringtoboolean[os.getenv('JSONNET_IMPLICIT_PLUS')] or false, + UseImplicitPlus = utils.stringtoboolean[os.getenv('JSONNET_IMPLICIT_PLUS')] or false, }, }, }) end + -- Set up DAP if requested local hasDap, dap = pcall(require, 'dap') if M.options.load_dap_config and hasDap then dap.adapters.jsonnet = { @@ -154,6 +175,9 @@ local function do_setup(options) }, } end + + -- Register type with treesitter, for `edgy.nvim` support + vim.treesitter.language.register('json', filetype) end M.setup = function(options) @@ -162,7 +186,15 @@ M.setup = function(options) callback = function(_) do_setup(options) - apply_mappings() + apply_mappings(M.options.keys) + apply_mappings(M.options.output_keys) + end, + }) + + vim.api.nvim_create_autocmd('FileType', { + pattern = { filetype }, + callback = function(_) + apply_mappings(M.options.output_keys) end, }) end diff --git a/lua/nvim-jsonnet/job.lua b/lua/nvim-jsonnet/job.lua new file mode 100644 index 0000000..d95123b --- /dev/null +++ b/lua/nvim-jsonnet/job.lua @@ -0,0 +1,89 @@ +local Job = require('plenary.job') + +--- @class JobResult +--- @field stdout string The standard output +--- @field stderr string The standard error +--- @field code number The exit code + +--- Run a job synchronously and return the results +--- @param cmd string The command to run +--- @param args string[] The command arguments +--- @param cwd string? The working directory +--- @return JobResult +local function run_job_sync(cmd, args, cwd) + local stdout = {} + local stderr = {} + local result = {} + + Job:new({ + command = cmd, + args = args, + cwd = cwd, + on_stdout = function(_, line) + table.insert(stdout, line) + end, + on_stderr = function(_, line) + table.insert(stderr, line) + end, + on_exit = function(_, code) + result.code = code + end, + }):sync() + + result.stdout = table.concat(stdout, '\n') + result.stderr = table.concat(stderr, '\n') + return result +end + +--- @class SystemResult +--- @field stdout string The standard output +--- @field stderr string The standard error +--- @field code number The exit code + +--- Execute a system command and return the result +--- @param cmd string[] The command and arguments as a table +--- @param cwd string? The working directory +--- @return SystemResult +local function system(cmd, cwd) + local result = vim.system(cmd, { + text = true, + cwd = cwd, + }):wait() + + return { + stdout = result.stdout or '', + stderr = result.stderr or '', + code = result.code or -1, + } +end + +--- Get the filename from a path +--- @param path string The file path +--- @return string The filename +local function filename(path) + return vim.fs.basename(path) +end + +--- Get the directory name from a path +--- @param path string The file path +--- @return string The directory name +local function dirname(path) + local parts = vim.split(path, '/') + table.remove(parts) + return table.concat(parts, '/') +end + +--- Check if a buffer is valid +--- @param buffer_number number The buffer number +--- @return boolean True if the buffer is valid and loaded +local function is_valid_buffer(buffer_number) + return buffer_number and vim.api.nvim_buf_is_valid(buffer_number) and vim.api.nvim_buf_is_loaded(buffer_number) +end + +return { + run_job_sync = run_job_sync, + system = system, + filename = filename, + dirname = dirname, + is_valid_buffer = is_valid_buffer, +} diff --git a/lua/nvim-jsonnet/utils.lua b/lua/nvim-jsonnet/utils.lua index bed146b..639143e 100644 --- a/lua/nvim-jsonnet/utils.lua +++ b/lua/nvim-jsonnet/utils.lua @@ -1,52 +1,4 @@ -function table.shallow_copy(t) - local t2 = {} - for k, v in pairs(t) do - t2[k] = v - end - return t2 -end - -local function getBuffer(name, filetype, opts) - local bufnr = vim.fn.bufnr(name) - if bufnr == -1 then - bufnr = vim.fn.bufadd(name) - vim.fn.win_execute(vim.fn.win_getid(1), string.format('%s sbuffer %d', opts.mods, bufnr)) - vim.api.nvim_buf_set_option(bufnr, 'buflisted', false) - vim.api.nvim_buf_set_option(bufnr, 'buftype', 'nofile') - vim.api.nvim_buf_set_option(bufnr, 'bufhidden', 'wipe') - vim.api.nvim_buf_set_option(bufnr, 'swapfile', false) - vim.api.nvim_buf_set_option(bufnr, 'filetype', filetype or '') - end - return bufnr -end - -local function runJob(cmd, args, filetype, opts) - local argsWithFile = table.shallow_copy(args) - argsWithFile[#argsWithFile + 1] = vim.fn.expand('%') - - local ex = require('plenary.job'):new({ - command = cmd, - args = argsWithFile, - cwd = vim.loop.cwd(), - enable_recording = true, - enabled_recording = true, - }) - - local stdout, code = ex:sync() - - if code ~= 0 then - local stderr = ex:stderr_result() - vim.notify( - ('cmd (%q) failed:\n%s'):format(cmd, vim.inspect(stderr)), - vim.log.levels.WARN - ) - return - end - - local bufnr = getBuffer(cmd .. ' ' .. table.concat(args, ' '), filetype, opts) - vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, stdout) -end - return { - RunCommand = runJob, + --- Convert a boolean-like string to a boolean value + stringtoboolean = { ['true'] = true, ['false'] = false }, } diff --git a/screenshots/edgy.png b/screenshots/edgy.png new file mode 100644 index 0000000000000000000000000000000000000000..a0399971883685afcc3abd26949f8ef5cdfc7209 GIT binary patch literal 28644 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 Date: Sun, 13 Apr 2025 17:12:53 +0100 Subject: [PATCH 9/9] docs: add markdownlint config to not conflict w/prettier, fix README Just some trivial lint errors that prettier & markdownlint pick up --- .markdownlint.json | 5 +++++ README.md | 10 ++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 .markdownlint.json diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 0000000..0fcd913 --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://raw.githubusercontent.com/DavidAnson/markdownlint/refs/heads/main/schema/markdownlint-config-schema.json", + + "extends": "markdownlint/style/prettier" +} diff --git a/README.md b/README.md index 26b076b..d6fda19 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ # nvim-jsonnet Features: -* Provide functions to evaluate Jsonnet code inside a split view -* Extend nvim-treesitter highlighting with references and linting + +- Provide functions to evaluate Jsonnet code inside a split view +- Extend nvim-treesitter highlighting with references and linting ## Usage @@ -41,9 +42,10 @@ This plugin does not provide syntax highlighting, folding, formatting or linting LSP with jsonnet-language-server provides formatting and linting out of the box, this config uses [nvim-lspconfig](https://github.com/neovim/nvim-lspconfig/). -See [Usage](#Usage) to enable opinionated setup. +See [Usage](#usage) to enable opinionated setup. Tip: configure format on save for all LSP buffers: + ```lua -- Format on save vim.api.nvim_create_autocmd( @@ -61,7 +63,7 @@ vim.api.nvim_create_autocmd( [nvim-dap](https://github.com/mfussenegger/nvim-dap) provides a way to run the [jsonnet-debugger](https://github.com/grafana/jsonnet-debugger), this works great in combination with [nvim-dap-ui](https://github.com/rcarriga/nvim-dap-ui). -Install the debugger with `go install github.com/grafana/jsonnet-debugger@v0.1.0` and see [Usage](#Usage) to enable. +Install the debugger with `go install github.com/grafana/jsonnet-debugger@v0.1.0` and see [Usage](#usage) to enable. ### Treesitter