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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,17 @@ The plugin is in early development.
## Features

- File types for Helm (including values.yaml files required for helm-ls)

- Highlight the current block (`if`, `with`, `range`)
- Jump between the start and end of a block with `%`
- experimental: Overwrite templates with their current values using virtual text (See [Demos](#demos))

- experimental: Show hints highlighting the effect of `nindent` and `indent` functions (See [Demos](#demos))

## Keymaps

The plugin adds the following keymaps for helm files:

- `%`: Jump between the start and end of a block (`if`, `with`, `range`)

## Installing

### Using `lazy.nvim`
Expand Down Expand Up @@ -54,6 +60,10 @@ Default config:
-- show the hints only for the line the cursor is on
only_for_current_line = true,
},
action_highlight = {
-- enable highlighting of the current block
enabled = true,
},
}
```

Expand Down
8 changes: 8 additions & 0 deletions ftplugin/helm.lua
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
-- set up the gotmpl commentstring
vim.opt_local.commentstring = "{{/* %s */}}"

vim.keymap.set("n", "%", function()
local jumped = require("helm-ls.matchparen").jump_to_matching_keyword()
if not jumped then
-- Fallback to default % behavior
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("%", true, false, true), "n", false)
end
end, { buffer = true, noremap = true, silent = true, desc = "Jump to matching keyword" })
20 changes: 18 additions & 2 deletions lua/helm-ls.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
---@class Config
---@field conceal_templates table
---@field indent_hints table
---@field action_highlight table
local config = {
conceal_templates = {
enabled = true,
Expand All @@ -11,6 +12,9 @@ local config = {
enabled = true,
only_for_current_line = true,
},
action_highlight = {
enabled = true,
},
}

---@class MyModule
Expand All @@ -29,12 +33,16 @@ M.setup = function(args)
if args.indent_hints and type(args.indent_hints) ~= "table" then
error("Helm-ls: Invalid type for indent_hints in config")
end
if args.action_highlight and type(args.action_highlight) ~= "table" then
error("Helm-ls: Invalid type for action_highlight in config")
end
end

M.config = vim.tbl_deep_extend("force", M.config, args or {})

local conceal = nil
local indent_hints = nil
local action_highlight = nil

if M.config.conceal_templates.enabled then
conceal = require("helm-ls.conceal")
Expand All @@ -46,7 +54,12 @@ M.setup = function(args)
indent_hints.set_config(M.config.indent_hints)
end

if not conceal and not indent_hints then
if M.config.action_highlight.enabled then
action_highlight = require("helm-ls.action_highlight")
action_highlight.setup(M.config.action_highlight)
end

if not conceal and not indent_hints and not action_highlight then
-- create no autocommand as the features are disabled
return
end
Expand All @@ -62,7 +75,7 @@ M.setup = function(args)
local group_id = vim.api.nvim_create_augroup("helm-ls.nvim", { clear = true })

-- Define file patterns as constants
local file_patterns = { "*.yaml", "*.yml", "*.helm", "*.tpl" }
local file_patterns = { "*.yaml", "*.yml", "*.helm", "*.tpl", "NOTES.txt" }

-- Define the autocommand
vim.api.nvim_create_autocmd({ "CursorMoved", "CursorMovedI" }, {
Expand All @@ -78,6 +91,9 @@ M.setup = function(args)
if conceal then
conceal.update_conceal_templates()
end
if action_highlight then
action_highlight.highlight_current_block()
end
end,
})
end
Expand Down
85 changes: 85 additions & 0 deletions lua/helm-ls/action_highlight.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
---@class ActionHighlightModule
local M = {}

local queries = require("helm-ls.queries")

local ns_id = vim.api.nvim_create_namespace("helm-ls-action-highlight")

local function highlight_node(bufnr, node)
if not node then
return
end
local start_row, start_col, end_row, end_col = node:range()
vim.api.nvim_buf_set_extmark(bufnr, ns_id, start_row, start_col, {
end_row = end_row,
end_col = end_col,
hl_group = "Visual",
})
end

local function highlight_keywords()
local bufnr = vim.api.nvim_get_current_buf()
vim.api.nvim_buf_clear_namespace(bufnr, ns_id, 0, -1)

local parser = vim.treesitter.get_parser(bufnr, "helm")
if not parser then
return
end

-- Make sure tree is parsed. parse() is idempotent.
parser:parse()

local cursor_node = vim.treesitter.get_node({ bufnr = bufnr, include_anonymous = true })
if not cursor_node then
return
end

-- 1. Find the containing action node by traversing up from cursor
local action_node
local current_node = cursor_node
local action_types = { "range_action", "if_action", "with_action", "define_action", "block_action" }
while current_node do
if vim.tbl_contains(action_types, current_node:type()) then
action_node = current_node
break
end
current_node = current_node:parent()
end

if not action_node then
return -- No action found at cursor
end

-- 2. Find parts within that action node and highlight them
local parts_query = vim.treesitter.query.parse("helm", queries.action_parts)
if not parts_query then
return
end

for id, node_to_highlight in parts_query:iter_captures(action_node, bufnr) do
local is_nested = false
local parent = node_to_highlight:parent()
-- Check if the capture is inside a nested action block
while parent and parent:id() ~= action_node:id() do
if vim.tbl_contains(action_types, parent:type()) then
is_nested = true
break
end
parent = parent:parent()
end

if not is_nested then
highlight_node(bufnr, node_to_highlight)
end
end
end

function M.setup(config)
-- Not needed for now
end

function M.highlight_current_block()
highlight_keywords()
end

return M
131 changes: 131 additions & 0 deletions lua/helm-ls/matchparen.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
---@class MatchParenModule
local M = {}

local queries = require("helm-ls.queries")

local function is_cursor_on_node(cursor_row, cursor_col, node)
local start_row, start_col, _, end_col = node:range()
if cursor_row == start_row and cursor_col >= start_col and cursor_col < end_col then
return true
end
return false
end

function M.jump_to_matching_keyword()
local bufnr = vim.api.nvim_get_current_buf()
local parser = vim.treesitter.get_parser(bufnr, "helm")
if not parser then
return false
end
parser:parse()

local cursor_row, cursor_col = unpack(vim.api.nvim_win_get_cursor(0))
cursor_row = cursor_row - 1

local cursor_node = vim.treesitter.get_node({ bufnr = bufnr, include_anonymous = true })
if not cursor_node then
return false
end

-- 1. Find the containing action node by traversing up from cursor
local action_node
local current_node = cursor_node
local action_types = { "range_action", "if_action", "with_action", "define_action", "block_action" }
while current_node do
if vim.tbl_contains(action_types, current_node:type()) then
action_node = current_node
break
end
current_node = current_node:parent()
end

if not action_node then
return false
end

-- 2. Get all parts, filter nested, and identify node under cursor
local parts_query = vim.treesitter.query.parse("helm", queries.action_parts)
if not parts_query then
return false
end

local start_nodes, middle_nodes, end_nodes = {}, {}, {}
local cursor_on_node

for id, node in parts_query:iter_captures(action_node, bufnr) do
local is_nested = false
local parent = node:parent()
while parent and parent:id() ~= action_node:id() do
if vim.tbl_contains(action_types, parent:type()) then
is_nested = true
break
end
parent = parent:parent()
end

if not is_nested then
if is_cursor_on_node(cursor_row, cursor_col, node) then
cursor_on_node = node
end
local capture_name = parts_query.captures[id]
if capture_name == "start" then
table.insert(start_nodes, node)
elseif capture_name == "middle" then
table.insert(middle_nodes, node)
elseif capture_name == "end" then
table.insert(end_nodes, node)
end
end
end

if not cursor_on_node then
return false
end

-- Sort nodes by position
table.sort(start_nodes, function(a, b) return a:start() < b:start() end)
table.sort(middle_nodes, function(a, b) return a:start() < b:start() end)
table.sort(end_nodes, function(a, b) return a:start() < b:start() end)

-- 3. Find which list the cursor_on_node is in and jump
local function jump_to(node)
local r, c = node:start()
vim.api.nvim_win_set_cursor(0, { r + 1, c })
return true
end

for i, node in ipairs(start_nodes) do
if node:id() == cursor_on_node:id() then
if #middle_nodes > 0 then
return jump_to(middle_nodes[1])
elseif #end_nodes > 0 then
return jump_to(end_nodes[1])
end
return false
end
end

for i, node in ipairs(middle_nodes) do
if node:id() == cursor_on_node:id() then
if i + 1 <= #middle_nodes then
return jump_to(middle_nodes[i + 1])
elseif #end_nodes > 0 then
return jump_to(end_nodes[1])
end
return false
end
end

for i, node in ipairs(end_nodes) do
if node:id() == cursor_on_node:id() then
if #start_nodes > 0 then
return jump_to(start_nodes[1])
end
return false
end
end

return false
end

return M
13 changes: 13 additions & 0 deletions lua/helm-ls/queries.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
local M = {}

M.action_parts = [[
("range" @start)
("if" @start)
("with" @start)
("define" @start)
("block" @start)
(["else" "else if"] @middle)
("end" @end)
]]

return M