From 73743890ebae3c827a949ddfcef133cac7758801 Mon Sep 17 00:00:00 2001 From: Rustum Zia Date: Fri, 30 Jan 2026 13:11:17 +0100 Subject: [PATCH 1/6] feat(indent): use proper shiftwidth for injected languages --- lua/nvim-treesitter/indent.lua | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lua/nvim-treesitter/indent.lua b/lua/nvim-treesitter/indent.lua index 6f8e4d8ff..13f8087f2 100644 --- a/lua/nvim-treesitter/indent.lua +++ b/lua/nvim-treesitter/indent.lua @@ -172,6 +172,15 @@ function M.get_indent(lnum) if root_start ~= 0 then -- injected tree indent = vim.fn.indent(root:start() + 1) + -- Inside an injected tree, we have a different language than the filetype of the current buffer, + -- so it doesn't make sense to use the value of 'shiftwidth' for the current buffer. That's why + -- we usew the default 'shiftwidth' for the injected language's filetype! + indent_size = vim.api.nvim_get_option_value('shiftwidth', { + -- Note: lang() returns the *language parser name*, which may be associated with multiple + -- filetypes. However, the language name is required to itself match the name of *one of + -- those* filetypes as well, which is why it is OK to pass as a filetype argument. + filetype = lang_tree:lang(), + }) end -- tracks to ensure multiple indent levels are not applied for same line From 930c60e9f7061dc2813ffb77d6efd768c861fa56 Mon Sep 17 00:00:00 2001 From: Rustum Zia Date: Fri, 30 Jan 2026 15:40:40 +0100 Subject: [PATCH 2/6] feat(indent) test mixed indents --- tests/indent/html/mixed_indent.html | 6 ++++++ tests/indent/html_spec.lua | 28 ++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 tests/indent/html/mixed_indent.html diff --git a/tests/indent/html/mixed_indent.html b/tests/indent/html/mixed_indent.html new file mode 100644 index 000000000..96ecdbdc9 --- /dev/null +++ b/tests/indent/html/mixed_indent.html @@ -0,0 +1,6 @@ + + + diff --git a/tests/indent/html_spec.lua b/tests/indent/html_spec.lua index b240b7150..4aee7e5ae 100644 --- a/tests/indent/html_spec.lua +++ b/tests/indent/html_spec.lua @@ -6,6 +6,33 @@ local runner = Runner:new(it, 'tests/indent/html', { }) describe('indent HTML:', function() + -- Test embedded language indent + local augroup + before_each(function() + augroup = vim.api.nvim_create_augroup('treesitter.tests.indent', {}) + vim.api.nvim_create_autocmd('FileType', { + group = augroup, + pattern = 'javascript', + callback = function() + -- use different indent for embedded js than html + vim.bo.tabstop = 4 + vim.bo.shiftwidth = 4 + end, + }) + local css_indent = vim.api.nvim_create_autocmd('FileType', { + group = augroup, + pattern = 'css', + callback = function() + -- use same indent for embedded css as html + vim.bo.tabstop = 2 + vim.bo.shiftwidth = 2 + end, + }) + end) + after_each(function() + vim.api.nvim_del_augroup_by_id(augroup) + end) + describe('whole file:', function() runner:whole_file('.') end) @@ -24,5 +51,6 @@ describe('indent HTML:', function() runner:new_line('script_style.html', { on_line = 6, text = '
', indent = 2 }) runner:new_line('script_style.html', { on_line = 9, text = 'const x = 1', indent = 2 }) runner:new_line('script_style.html', { on_line = 11, text = 'Text', indent = 2 }) + runner:new_line('mixed_indent.html', { on_line = 3, text = 'const x = 1;', indent = 6 }) end) end) From cd1cd5484972e1bc1ddfa00999c6f215efd6b481 Mon Sep 17 00:00:00 2001 From: Rustum Zia Date: Thu, 26 Feb 2026 15:27:37 +0100 Subject: [PATCH 3/6] feeling OK --- lua/nvim-treesitter/indent.lua | 45 +++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/lua/nvim-treesitter/indent.lua b/lua/nvim-treesitter/indent.lua index 13f8087f2..6afca3498 100644 --- a/lua/nvim-treesitter/indent.lua +++ b/lua/nvim-treesitter/indent.lua @@ -107,6 +107,41 @@ end, function(bufnr, root, lang) return tostring(bufnr) .. root:id() .. '_' .. lang end) +local get_language_shiftwidth = memoize(function(bufnr, lang) + local buffer_shiftwidth = vim.bo.shiftwidth + local global_shiftwidth = vim.go.shiftwidth + -- See :h 'shiftwidth': If set to 0, should use tabstop (0 is not the default value, + -- but users may rely on this behavior) + if buffer_shiftwidth == 0 then + buffer_shiftwidth = vim.bo.tabstop + end + if global_shiftwidth == 0 then + global_shiftwidth = vim.bo.tabstop + end + + local lang_shiftwidth = nil + -- Note: get_filetypes(lang) always includes `lang` in the returned array of filetypes even if + -- `lang` is not a filetype + local filetypes = vim.treesitter.language.get_filetypes(lang) + for _, ft in ipairs(filetypes) do + -- filetype.get_option will default to the global value for the option + -- if (1) there is no local equivalent set, or (2) the filetype does not exist + local filetype_shiftwidth = vim.filetype.get_option(ft, 'shiftwidth') + if filetype_shiftwidth == 0 then + filetype_shiftwidth = vim.filetype.get_option(ft, 'tabstop') + end + if filetype_shiftwidth ~= global_shiftwidth then + lang_shiftwidth = filetype_shiftwidth + end + end + + -- if lang_shiftwidth is nil, then it is either unset OR it is the same as + -- global_shiftwidth + return lang_shiftwidth or global_shiftwidth or buffer_shiftwidth +end, function(bufnr, lang) + return tostring(bufnr) .. '_' .. lang +end) + ---@param lnum integer (1-indexed) ---@return integer function M.get_indent(lnum) @@ -172,15 +207,7 @@ function M.get_indent(lnum) if root_start ~= 0 then -- injected tree indent = vim.fn.indent(root:start() + 1) - -- Inside an injected tree, we have a different language than the filetype of the current buffer, - -- so it doesn't make sense to use the value of 'shiftwidth' for the current buffer. That's why - -- we usew the default 'shiftwidth' for the injected language's filetype! - indent_size = vim.api.nvim_get_option_value('shiftwidth', { - -- Note: lang() returns the *language parser name*, which may be associated with multiple - -- filetypes. However, the language name is required to itself match the name of *one of - -- those* filetypes as well, which is why it is OK to pass as a filetype argument. - filetype = lang_tree:lang(), - }) + indent_size = get_language_shiftwidth(bufnr, lang_tree:lang()) end -- tracks to ensure multiple indent levels are not applied for same line From ed14c4d8559031c801ead597d2a4f99c29d3e472 Mon Sep 17 00:00:00 2001 From: Rustum Zia Date: Thu, 26 Feb 2026 15:51:00 +0100 Subject: [PATCH 4/6] have to default to global shiftwidth --- lua/nvim-treesitter/indent.lua | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lua/nvim-treesitter/indent.lua b/lua/nvim-treesitter/indent.lua index 6afca3498..652263e36 100644 --- a/lua/nvim-treesitter/indent.lua +++ b/lua/nvim-treesitter/indent.lua @@ -107,8 +107,11 @@ end, function(bufnr, root, lang) return tostring(bufnr) .. root:id() .. '_' .. lang end) +---@param bufnr integer +---@param lang string +---@return integer local get_language_shiftwidth = memoize(function(bufnr, lang) - local buffer_shiftwidth = vim.bo.shiftwidth + ---@type integer local global_shiftwidth = vim.go.shiftwidth -- See :h 'shiftwidth': If set to 0, should use tabstop (0 is not the default value, -- but users may rely on this behavior) @@ -119,16 +122,19 @@ local get_language_shiftwidth = memoize(function(bufnr, lang) global_shiftwidth = vim.bo.tabstop end + ---@type integer|nil local lang_shiftwidth = nil -- Note: get_filetypes(lang) always includes `lang` in the returned array of filetypes even if -- `lang` is not a filetype + ---@type string[] local filetypes = vim.treesitter.language.get_filetypes(lang) for _, ft in ipairs(filetypes) do -- filetype.get_option will default to the global value for the option -- if (1) there is no local equivalent set, or (2) the filetype does not exist + ---@type integer local filetype_shiftwidth = vim.filetype.get_option(ft, 'shiftwidth') if filetype_shiftwidth == 0 then - filetype_shiftwidth = vim.filetype.get_option(ft, 'tabstop') + filetype_shiftwidth = vim.filetype.get_option(ft, 'tabstop')--[[@as integer]] end if filetype_shiftwidth ~= global_shiftwidth then lang_shiftwidth = filetype_shiftwidth @@ -137,7 +143,7 @@ local get_language_shiftwidth = memoize(function(bufnr, lang) -- if lang_shiftwidth is nil, then it is either unset OR it is the same as -- global_shiftwidth - return lang_shiftwidth or global_shiftwidth or buffer_shiftwidth + return lang_shiftwidth or global_shiftwidth end, function(bufnr, lang) return tostring(bufnr) .. '_' .. lang end) From 43f458a15356adbeec390db993ffe9ad6eca1697 Mon Sep 17 00:00:00 2001 From: Rustum Zia Date: Thu, 26 Feb 2026 18:06:37 +0100 Subject: [PATCH 5/6] lint --- lua/nvim-treesitter/indent.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/nvim-treesitter/indent.lua b/lua/nvim-treesitter/indent.lua index 652263e36..993057a48 100644 --- a/lua/nvim-treesitter/indent.lua +++ b/lua/nvim-treesitter/indent.lua @@ -132,7 +132,7 @@ local get_language_shiftwidth = memoize(function(bufnr, lang) -- filetype.get_option will default to the global value for the option -- if (1) there is no local equivalent set, or (2) the filetype does not exist ---@type integer - local filetype_shiftwidth = vim.filetype.get_option(ft, 'shiftwidth') + local filetype_shiftwidth = vim.filetype.get_option(ft, 'shiftwidth')--[[@as integer]] if filetype_shiftwidth == 0 then filetype_shiftwidth = vim.filetype.get_option(ft, 'tabstop')--[[@as integer]] end From d098854a9c263cfd17d319dfc91dda1a9dd220e9 Mon Sep 17 00:00:00 2001 From: Rustum Zia Date: Thu, 26 Feb 2026 18:11:58 +0100 Subject: [PATCH 6/6] Line got reverted --- lua/nvim-treesitter/indent.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lua/nvim-treesitter/indent.lua b/lua/nvim-treesitter/indent.lua index 993057a48..b05023cef 100644 --- a/lua/nvim-treesitter/indent.lua +++ b/lua/nvim-treesitter/indent.lua @@ -113,6 +113,8 @@ end) local get_language_shiftwidth = memoize(function(bufnr, lang) ---@type integer local global_shiftwidth = vim.go.shiftwidth + ---@type integer + local buffer_shiftwidth = vim.bo.shiftwidth -- See :h 'shiftwidth': If set to 0, should use tabstop (0 is not the default value, -- but users may rely on this behavior) if buffer_shiftwidth == 0 then