Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,6 @@

### Installation

> [!IMPORTANT]
> This plugin requires the Lua [lyaml](https://luarocks.org/modules/gvvaughan/lyaml) library for YAML parsing.

Use your favorite Neovim plugin manager.

<details>
Expand Down
4 changes: 2 additions & 2 deletions docs/project-description.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Each extension is independently configurable and can be enabled or disabled as n
- The extension can be enabled or disabled via the `enabled` config field.

- **Frontmatter Parsing:**
- The extension parses YAML frontmatter from markdown files using a Lua YAML parser (`lyaml` or `yaml.nvim`).
- The extension parses YAML frontmatter from markdown files using a built-in lightweight YAML parser.

- **Slash Command Patching:**
- The `/buffer` slash command is patched so that after a buffer is added, the extension injects the appropriate instruction files.
Expand All @@ -36,7 +36,7 @@ Each extension is independently configurable and can be enabled or disabled as n
## Technical Notes

- The extension caches the mapping between globs and custom instruction files in memory for performance.
- If no YAML parser is available, the extension notifies the user and skips conditional custom instructions.
- The extension includes a lightweight YAML parser optimized for frontmatter parsing.
- The extension is designed to be robust and extensible, following CodeCompanion's extension guidelines.

## Goals
Expand Down
4 changes: 2 additions & 2 deletions lua/codecompanion/filewise/frontmatter.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
-- filewise/frontmatter – Utilities for parsing YAML frontmatter from markdown
--=============================================================================

local lyaml = require 'lyaml'
local yaml_parser = require 'codecompanion.filewise.yaml_parser'

local M = {}

Expand Down Expand Up @@ -31,7 +31,7 @@ function M.parse(path, rest)
if rest then table.insert(body, l) else break end
end
end
local ok, fm = pcall(lyaml.load, table.concat(frontmatter, '\n'))
local ok, fm = pcall(yaml_parser.parse, table.concat(frontmatter, '\n'))
if ok and type(fm) == 'table' then
return fm, body
end
Expand Down
145 changes: 145 additions & 0 deletions lua/codecompanion/filewise/yaml_parser.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
--=============================================================================
-- filewise/yaml_parser – Simple YAML parser for frontmatter
--=============================================================================

local M = {}

--- Trim leading and trailing whitespace from a string.
---@param s string|nil
---@return string|nil
local function trim(s)
if not s then return s end
return s:match('^%s*(.-)%s*$')
end

--- Remove a single layer of surrounding quotes from a string if present.
---@param s string|nil
---@return string|nil
local function unquote(s)
if not s then return s end
return s:match('^"(.-)"$') or s:match("^'(.-)'$") or s
end

--- Strip only the leading indentation from a string (keep trailing spaces intact).
---@param s string
---@return string
local function ltrim(s)
return (s:gsub('^%s+', ''))
end

--- Simple YAML parser for basic key-value frontmatter
--- Supports:
--- - simple key: value pairs
--- - inline lists: key: [a, b, c]
--- - dash lists:
--- key:
--- - a
--- - b
--- - block scalars using | or >
--- Notes/limitations (by design for frontmatter use-cases):
--- - keys are limited to [A-Za-z0-9_-]
--- - inline lists don't support commas inside quoted strings
--- - scalar values are returned as strings (no auto-typing)
---@param yaml_text string|nil The YAML content to parse
---@return table<string, string|string[]>|nil Parsed YAML as a table, or nil if invalid/empty
function M.parse(yaml_text)
if not yaml_text or yaml_text == "" then
return nil
end

local result = {}
local lines = {}
for l in yaml_text:gmatch('[^\r\n]+') do
table.insert(lines, l)
end

local i = 1
while i <= #lines do
local raw = lines[i]
local line = trim(raw)
-- skip empty and comment lines
if line == '' or line:match('^#') then
i = i + 1
else
local key, value = line:match('^([%w_%-]+)%s*:%s*(.*)$')
Copy link

Copilot AI Sep 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The key pattern [%w_%-]+ allows hyphens anywhere in the key name, but YAML keys with leading/trailing hyphens or consecutive hyphens may cause parsing issues. Consider using a more restrictive pattern like [%w_]+[%w_%-]*[%w_]+ or [%w_][%w_%-]* to ensure valid key names.

Suggested change
local key, value = line:match('^([%w_%-]+)%s*:%s*(.*)$')
local key, value = line:match('^([%w_]+[%w_%-]*[%w_]+)%s*:%s*(.*)$')

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure about this one...

if key then
-- empty value -> might be a dash list or block following
if value == '' then
-- check for dash list on following lines
local items = {}
local j = i + 1
while j <= #lines do
local nxt = lines[j]
if nxt:match('^%s*%-%s+') then
local item = nxt:match('^%s*%-%s+(.*)$')
item = trim(item)
item = unquote(item)
table.insert(items, item)
j = j + 1
elseif nxt:match('^%s*$') then
j = j + 1
else
break
end
end
if #items > 0 then
result[key] = items
i = j
else
-- nothing special; store empty string
result[key] = ''
i = i + 1
end
else
-- inline list? e.g. [a, b, c]
local inline = value:match('^%[(.*)%]$')
if inline then
local items = {}
for part in inline:gmatch('[^,]+') do
Copy link

Copilot AI Sep 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This parsing logic will incorrectly split quoted strings containing commas. For example, [\"a, b\", \"c\"] will be split into three parts instead of two. The comment on line 41 acknowledges this limitation, but it could cause issues with frontmatter that contains quoted strings with commas.

Copilot uses AI. Check for mistakes.
local v = trim(part)
v = unquote(v)
table.insert(items, v)
end
result[key] = items
i = i + 1
elseif value == '|' or value == '>' then
-- block scalar: collect following indented lines (start with whitespace)
local j = i + 1
local buf = {}
while j <= #lines do
local nxt = lines[j]
if nxt:match('^%s+$') then
-- preserve blank line inside block scalar
table.insert(buf, '')
j = j + 1
elseif nxt:match('^%s+') then
-- strip only the leading indentation for readability
table.insert(buf, ltrim(nxt))
j = j + 1
else
break
end
end
result[key] = table.concat(buf, '\n')
i = j
else
local v = trim(value)
v = unquote(v)
result[key] = v
i = i + 1
end
end
else
-- not a key line; ignore
i = i + 1
end
end
end

if next(result) == nil then
return nil
end
return result
end

return M