diff --git a/lua/nvim-treesitter/indent.lua b/lua/nvim-treesitter/indent.lua index ee513c351..0e1aa9c01 100644 --- a/lua/nvim-treesitter/indent.lua +++ b/lua/nvim-treesitter/indent.lua @@ -107,6 +107,49 @@ 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) + ---@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 + buffer_shiftwidth = vim.bo.tabstop + end + if global_shiftwidth == 0 then + 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')--[[@as integer]] + if filetype_shiftwidth == 0 then + filetype_shiftwidth = vim.filetype.get_option(ft, 'tabstop')--[[@as integer]] + 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 +end, function(bufnr, lang) + return tostring(bufnr) .. '_' .. lang +end) + ---@param lnum integer (1-indexed) ---@return integer function M.get_indent(lnum) @@ -172,6 +215,7 @@ function M.get_indent(lnum) if root_start ~= 0 then -- injected tree indent = vim.fn.indent(root:start() + 1) + indent_size = get_language_shiftwidth(bufnr, lang_tree:lang()) end -- tracks to ensure multiple indent levels are not applied for same line 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)