From 98b2e19c026a679a708626b96482cd2680cd6393 Mon Sep 17 00:00:00 2001 From: Benny Powers Date: Tue, 21 Apr 2026 18:28:39 +0300 Subject: [PATCH 1/2] feat(yaml): split scalar flow sequences into block-style lists Splitting a flow_sequence like `[opened, synchronize, reopened]` now produces idiomatic YAML block-style with `- ` prefixes instead of bracketed multi-line. Joining a block_sequence reverses the operation, merging items back onto the mapping key's line. Flow sequences containing non-scalar items (nested mappings or sequences) still fall back to the bracket-preserving default. Assisted-By: Claude Opus 4.6 (1M context) --- README.md | 2 +- lua/splitjoin/languages/yaml/defaults.lua | 5 +- lua/splitjoin/languages/yaml/functions.lua | 107 +++++++++++++++++++++ queries/yaml/splitjoin.scm | 1 + test/yaml_spec.lua | 43 +++++++-- 5 files changed, 150 insertions(+), 8 deletions(-) create mode 100644 lua/splitjoin/languages/yaml/functions.lua diff --git a/README.md b/README.md index 328ec0c..52c7308 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ After: - **c++**: all C constructs, plus template parameters and arguments - **nix**: lists - **json**: objects, arrays -- **yaml**: flow sequences, flow mappings +- **yaml**: flow sequences, flow mappings, block sequences - **jsdoc**: descriptions ### Adding a new language diff --git a/lua/splitjoin/languages/yaml/defaults.lua b/lua/splitjoin/languages/yaml/defaults.lua index 0e8e795..ed03f97 100644 --- a/lua/splitjoin/languages/yaml/defaults.lua +++ b/lua/splitjoin/languages/yaml/defaults.lua @@ -1,9 +1,12 @@ +local Yaml = require'splitjoin.languages.yaml.functions' + ---@type SplitjoinLanguageConfig return { nodes = { - flow_sequence = { surround = { '[', ']' } }, + flow_sequence = { surround = { '[', ']' }, split = Yaml.split_flow_sequence }, flow_mapping = { surround = { '{', '}' } }, + block_sequence = { split = function() end, join = Yaml.join_block_sequence }, }, } diff --git a/lua/splitjoin/languages/yaml/functions.lua b/lua/splitjoin/languages/yaml/functions.lua new file mode 100644 index 0000000..1df82f4 --- /dev/null +++ b/lua/splitjoin/languages/yaml/functions.lua @@ -0,0 +1,107 @@ +local Node = require'splitjoin.util.node' + +local Yaml = {} + +local function all_scalars(node) + return not Node.find_descendant(node, function(n) + if n == node then return false end + local t = n:type() + return t == 'flow_sequence' or t == 'flow_mapping' + end) +end + +local function in_flow_context(node) + local parent = node:parent() + while parent do + local t = parent:type() + if t == 'flow_sequence' or t == 'flow_mapping' then + return true + end + parent = parent:parent() + end + return false +end + +function Yaml.split_flow_sequence(node, options) + if not all_scalars(node) or in_flow_context(node) then + return Node.split(node, options) + end + + local indent = options.default_indent or ' ' + local items = {} + for child in node:iter_children() do + local t = child:type() + if t ~= '[' and t ~= ']' and t ~= ',' then + table.insert(items, vim.trim(Node.get_text(child))) + end + end + + if #items == 0 then return end + + local row, col, row_end, col_end = node:range() + local base_indent = Node.get_base_indent(node) or '' + local line = vim.api.nvim_buf_get_lines(0, row, row + 1, false)[1] + local before = line:sub(1, col) + + local lines = {} + local start_col = col + + if before:match(':%s*$') then + local content_len = #before:match('^(.-)%s*$') + start_col = content_len + table.insert(lines, '') + for _, item in ipairs(items) do + table.insert(lines, base_indent .. indent .. '- ' .. item) + end + else + for _, item in ipairs(items) do + table.insert(lines, base_indent .. '- ' .. item) + end + start_col = 0 + end + + vim.api.nvim_buf_set_text(0, row, start_col, row_end, col_end, lines) + pcall(vim.api.nvim_win_set_cursor, 0, { row + 2, #(base_indent .. indent) }) +end + +function Yaml.join_block_sequence(node, options) + local items = {} + for child in node:iter_children() do + if child:type() == 'block_sequence_item' then + for grandchild in child:iter_children() do + if grandchild:type() ~= '-' then + local text = vim.trim(Node.get_text(grandchild)) + if text ~= '' then + table.insert(items, text) + end + end + end + end + end + + if #items == 0 then return end + + local row, col, row_end, col_end = node:range() + local replacement = '[' .. table.concat(items, ', ') .. ']' + + local line_count = vim.api.nvim_buf_line_count(0) + if row_end >= line_count then + row_end = line_count - 1 + col_end = #vim.api.nvim_buf_get_lines(0, row_end, row_end + 1, false)[1] + end + + if row > 0 then + local prev_line = vim.api.nvim_buf_get_lines(0, row - 1, row, false)[1] + local colon_pos = prev_line:find(':%s*$') + if colon_pos then + vim.api.nvim_buf_set_text(0, row - 1, colon_pos, row_end, col_end, { ' ' .. replacement }) + pcall(vim.api.nvim_win_set_cursor, 0, { row, colon_pos + 1 }) + return + end + end + + vim.api.nvim_buf_set_text(0, row, col, row_end, col_end, { replacement }) + pcall(vim.api.nvim_win_set_cursor, 0, { row + 1, col }) +end + +return Yaml diff --git a/queries/yaml/splitjoin.scm b/queries/yaml/splitjoin.scm index f5adb5d..3403924 100644 --- a/queries/yaml/splitjoin.scm +++ b/queries/yaml/splitjoin.scm @@ -1,2 +1,3 @@ (flow_sequence) @splitjoin.target (flow_mapping) @splitjoin.target +(block_sequence) @splitjoin.target diff --git a/test/yaml_spec.lua b/test/yaml_spec.lua index 62cc02e..c8f9293 100644 --- a/test/yaml_spec.lua +++ b/test/yaml_spec.lua @@ -5,20 +5,51 @@ local lang = 'yaml' describe(lang, function() - H.make_suite(lang, 'flow sequence', + H.make_suite(lang, 'flow sequence (scalar, block-style)', d[[ list: [1, 2, 3] ]], d[[ - list: [ - 1, - 2, - 3, - ] + list: + - 1 + - 2 + - 3 ]], '1' ) + H.make_suite(lang, 'flow sequence (non-scalar, bracket-style)', + d[=[ + awful: [string, 0, {refactor: this}, pal] + ]=], + d[=[ + awful: [ + string, + 0, + {refactor: this}, + pal, + ] + ]=], + 'string' + ) + + H.make_suite(lang, 'flow sequence (nested indent)', + d[[ + on: + pull_request: + types: [opened, synchronize, reopened] + ]], + d[[ + on: + pull_request: + types: + - opened + - synchronize + - reopened + ]], + 'opened' + ) + H.make_suite(lang, 'flow mapping', d[=[ map: {a: 1, b: 2} From 8ddd823f1ad3336bb4e344c0d87b999fa8456e10 Mon Sep 17 00:00:00 2001 From: Benny Powers Date: Tue, 21 Apr 2026 18:42:52 +0300 Subject: [PATCH 2/2] fix(yaml): correct cursor position for standalone flow sequence split Assisted-By: Claude Opus 4.6 (1M context) --- lua/splitjoin/languages/yaml/functions.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lua/splitjoin/languages/yaml/functions.lua b/lua/splitjoin/languages/yaml/functions.lua index 1df82f4..c801726 100644 --- a/lua/splitjoin/languages/yaml/functions.lua +++ b/lua/splitjoin/languages/yaml/functions.lua @@ -61,7 +61,11 @@ function Yaml.split_flow_sequence(node, options) end vim.api.nvim_buf_set_text(0, row, start_col, row_end, col_end, lines) - pcall(vim.api.nvim_win_set_cursor, 0, { row + 2, #(base_indent .. indent) }) + if start_col == 0 then + pcall(vim.api.nvim_win_set_cursor, 0, { row + 1, #base_indent }) + else + pcall(vim.api.nvim_win_set_cursor, 0, { row + 2, #(base_indent .. indent) }) + end end function Yaml.join_block_sequence(node, options)