local utils = require "nvim-tree.utils" local view = require "nvim-tree.view" local _padding = require "nvim-tree.renderer.padding" local _help = require "nvim-tree.renderer.help" local _icons = require "nvim-tree.renderer.icons" local api = vim.api local lines = {} local hl = {} local index = 0 local namespace_id = api.nvim_create_namespace "NvimTreeHighlights" local icon_state = _icons.get_config() local should_hl_opened_files = (vim.g.nvim_tree_highlight_opened_files or 0) ~= 0 local get_folder_icon = function() return "" end local function get_trailing_length() return vim.g.nvim_tree_add_trailing and 1 or 0 end local set_folder_hl = function(line, depth, git_icon_len, _, hl_group) table.insert(hl, { hl_group, line, depth + git_icon_len, -1 }) end local icon_padding = vim.g.nvim_tree_icon_padding or " " if icon_state.show_folder_icon then get_folder_icon = function(open, is_symlink, has_children) local n if is_symlink and open then n = icon_state.icons.folder_icons.symlink_open elseif is_symlink then n = icon_state.icons.folder_icons.symlink elseif open then if has_children then n = icon_state.icons.folder_icons.open else n = icon_state.icons.folder_icons.empty_open end else if has_children then n = icon_state.icons.folder_icons.default else n = icon_state.icons.folder_icons.empty end end return n .. icon_padding end set_folder_hl = function(line, depth, icon_len, name_len, hl_group) table.insert(hl, { hl_group, line, depth + icon_len, depth + icon_len + name_len + get_trailing_length() }) local hl_icon = (vim.g.nvim_tree_highlight_opened_files or 0) ~= 0 and hl_group or "NvimTreeFolderIcon" table.insert(hl, { hl_icon, line, depth, depth + icon_len }) end end local get_file_icon = function() return "" end if icon_state.show_file_icon then if icon_state.has_devicons then local web_devicons = require "nvim-web-devicons" get_file_icon = function(fname, extension, line, depth) local icon, hl_group = web_devicons.get_icon(fname, extension) if icon and hl_group ~= "DevIconDefault" then if hl_group then table.insert(hl, { hl_group, line, depth, depth + #icon + 1 }) end return icon .. icon_padding elseif string.match(extension, "%.(.*)") then -- If there are more extensions to the file, try to grab the icon for them recursively return get_file_icon(fname, string.match(extension, "%.(.*)"), line, depth) else return #icon_state.icons.default > 0 and icon_state.icons.default .. icon_padding or "" end end else get_file_icon = function() return #icon_state.icons.default > 0 and icon_state.icons.default .. icon_padding or "" end end end local get_symlink_icon = function() return icon_state.icons.symlink end if icon_state.show_file_icon then get_symlink_icon = function() return #icon_state.icons.symlink > 0 and icon_state.icons.symlink .. icon_padding or "" end end local get_special_icon = function() return "" end if icon_state.show_file_icon then get_special_icon = function() return #icon_state.icons.default > 0 and icon_state.icons.default .. icon_padding or "" end end local get_git_icons = function() return "" end local get_git_hl = function() end if vim.g.nvim_tree_git_hl == 1 then local git_hl = { ["M "] = { { hl = "NvimTreeFileStaged" } }, [" M"] = { { hl = "NvimTreeFileDirty" } }, ["CM"] = { { hl = "NvimTreeFileDirty" } }, ["C "] = { { hl = "NvimTreeFileStaged" } }, [" C"] = { { hl = "NvimTreeFileDirty" } }, [" T"] = { { hl = "NvimTreeFileDirty" } }, ["MM"] = { { hl = "NvimTreeFileStaged" }, { hl = "NvimTreeFileDirty" }, }, ["A "] = { { hl = "NvimTreeFileStaged" }, { hl = "NvimTreeFileNew" }, }, ["AU"] = { { hl = "NvimTreeFileMerge" }, { hl = "NvimTreeFileStaged" }, }, -- not sure about this one ["AA"] = { { hl = "NvimTreeFileMerge" }, { hl = "NvimTreeFileStaged" }, }, ["AD"] = { { hl = "NvimTreeFileStaged" }, }, ["MD"] = { { hl = "NvimTreeFileStaged" }, }, ["AM"] = { { hl = "NvimTreeFileStaged" }, { hl = "NvimTreeFileNew" }, { hl = "NvimTreeFileDirty" }, }, ["??"] = { { hl = "NvimTreeFileNew" } }, ["R "] = { { hl = "NvimTreeFileRenamed" } }, ["UU"] = { { hl = "NvimTreeFileMerge" } }, ["UD"] = { { hl = "NvimTreeFileMerge" } }, ["UA"] = { { hl = "NvimTreeFileMerge" } }, [" D"] = { { hl = "NvimTreeFileDeleted" } }, ["DD"] = { { hl = "NvimTreeFileDeleted" } }, ["RD"] = { { hl = "NvimTreeFileDeleted" } }, ["D "] = { { hl = "NvimTreeFileDeleted" }, { hl = "NvimTreeFileStaged" }, }, ["DU"] = { { hl = "NvimTreeFileDeleted" }, { hl = "NvimTreeFileMerge" }, }, [" A"] = { { hl = "none" } }, ["RM"] = { { hl = "NvimTreeFileRenamed" } }, [" R"] = { { hl = "NvimTreeFileRenamed" } }, ["!!"] = { { hl = "NvimTreeGitIgnored" } }, dirty = { { hl = "NvimTreeFileDirty" } }, } get_git_hl = function(node) local git_status = node.git_status if not git_status then return end local icons = git_hl[git_status] if icons == nil then utils.warn( 'Unrecognized git state "' .. git_status .. '". Please open up an issue on https://github.com/kyazdani42/nvim-tree.lua/issues with this message.' ) icons = git_hl.dirty end -- TODO: how would we determine hl color when multiple git status are active ? return icons[1].hl -- return icons[#icons].hl end end if icon_state.show_git_icon then local git_icon_state = { ["M "] = { { icon = icon_state.icons.git_icons.staged, hl = "NvimTreeGitStaged" } }, [" M"] = { { icon = icon_state.icons.git_icons.unstaged, hl = "NvimTreeGitDirty" } }, ["C "] = { { icon = icon_state.icons.git_icons.staged, hl = "NvimTreeGitStaged" } }, [" C"] = { { icon = icon_state.icons.git_icons.unstaged, hl = "NvimTreeGitDirty" } }, ["CM"] = { { icon = icon_state.icons.git_icons.unstaged, hl = "NvimTreeGitDirty" } }, [" T"] = { { icon = icon_state.icons.git_icons.unstaged, hl = "NvimTreeGitDirty" } }, ["MM"] = { { icon = icon_state.icons.git_icons.staged, hl = "NvimTreeGitStaged" }, { icon = icon_state.icons.git_icons.unstaged, hl = "NvimTreeGitDirty" }, }, ["MD"] = { { icon = icon_state.icons.git_icons.staged, hl = "NvimTreeGitStaged" }, }, ["A "] = { { icon = icon_state.icons.git_icons.staged, hl = "NvimTreeGitStaged" }, }, ["AD"] = { { icon = icon_state.icons.git_icons.staged, hl = "NvimTreeGitStaged" }, }, [" A"] = { { icon = icon_state.icons.git_icons.untracked, hl = "NvimTreeGitNew" }, }, -- not sure about this one ["AA"] = { { icon = icon_state.icons.git_icons.unmerged, hl = "NvimTreeGitMerge" }, { icon = icon_state.icons.git_icons.untracked, hl = "NvimTreeGitNew" }, }, ["AU"] = { { icon = icon_state.icons.git_icons.unmerged, hl = "NvimTreeGitMerge" }, { icon = icon_state.icons.git_icons.untracked, hl = "NvimTreeGitNew" }, }, ["AM"] = { { icon = icon_state.icons.git_icons.staged, hl = "NvimTreeGitStaged" }, { icon = icon_state.icons.git_icons.unstaged, hl = "NvimTreeGitDirty" }, }, ["??"] = { { icon = icon_state.icons.git_icons.untracked, hl = "NvimTreeGitNew" } }, ["R "] = { { icon = icon_state.icons.git_icons.renamed, hl = "NvimTreeGitRenamed" } }, [" R"] = { { icon = icon_state.icons.git_icons.renamed, hl = "NvimTreeGitRenamed" } }, ["RM"] = { { icon = icon_state.icons.git_icons.unstaged, hl = "NvimTreeGitDirty" }, { icon = icon_state.icons.git_icons.renamed, hl = "NvimTreeGitRenamed" }, }, ["UU"] = { { icon = icon_state.icons.git_icons.unmerged, hl = "NvimTreeGitMerge" } }, ["UD"] = { { icon = icon_state.icons.git_icons.unmerged, hl = "NvimTreeGitMerge" } }, ["UA"] = { { icon = icon_state.icons.git_icons.unmerged, hl = "NvimTreeGitMerge" } }, [" D"] = { { icon = icon_state.icons.git_icons.deleted, hl = "NvimTreeGitDeleted" } }, ["D "] = { { icon = icon_state.icons.git_icons.deleted, hl = "NvimTreeGitDeleted" } }, ["RD"] = { { icon = icon_state.icons.git_icons.deleted, hl = "NvimTreeGitDeleted" } }, ["DD"] = { { icon = icon_state.icons.git_icons.deleted, hl = "NvimTreeGitDeleted" } }, ["DU"] = { { icon = icon_state.icons.git_icons.deleted, hl = "NvimTreeGitDeleted" }, { icon = icon_state.icons.git_icons.unmerged, hl = "NvimTreeGitMerge" }, }, ["!!"] = { { icon = icon_state.icons.git_icons.ignored, hl = "NvimTreeGitIgnored" } }, dirty = { { icon = icon_state.icons.git_icons.unstaged, hl = "NvimTreeGitDirty" } }, } get_git_icons = function(node, line, depth, icon_len) local git_status = node.git_status if not git_status then return "" end local icon = "" local icons = git_icon_state[git_status] if not icons then if vim.g.nvim_tree_git_hl ~= 1 then utils.warn( 'Unrecognized git state "' .. git_status .. '". Please open up an issue on https://github.com/kyazdani42/nvim-tree.lua/issues with this message.' ) end icons = git_icon_state.dirty end for _, v in ipairs(icons) do if #v.icon > 0 then table.insert(hl, { v.hl, line, depth + icon_len + #icon, depth + icon_len + #icon + #v.icon }) icon = icon .. v.icon .. icon_padding end end return icon end end local picture = { jpg = true, jpeg = true, png = true, gif = true, } local function update_draw_data(tree, depth, markers) local root_folder_modifier = vim.g.nvim_tree_root_folder_modifier or ":~" local special = vim.g.nvim_tree_special_files or { ["Cargo.toml"] = true, Makefile = true, ["README.md"] = true, ["readme.md"] = true, } local hide_root_folder = view.View.hide_root_folder if tree.cwd and tree.cwd ~= "/" and not hide_root_folder then local root_name = utils.path_join { utils.path_remove_trailing(vim.fn.fnamemodify(tree.cwd, root_folder_modifier)), "..", } table.insert(lines, root_name) table.insert(hl, { "NvimTreeRootFolder", index, 0, string.len(root_name) }) index = 1 end for idx, node in ipairs(tree.nodes) do local padding = _padding.get_padding(depth, idx, tree, node, markers) local offset = string.len(padding) if depth > 0 then table.insert(hl, { "NvimTreeIndentMarker", index, 0, offset }) end local git_hl = get_git_hl(node) if node.nodes then local has_children = #node.nodes ~= 0 or node.has_children local icon = get_folder_icon(node.open, node.link_to ~= nil, has_children) local git_icon = get_git_icons(node, index, offset, #icon) or "" -- INFO: this is mandatory in order to keep gui attributes (bold/italics) local folder_hl = "NvimTreeFolderName" local name = node.name local next = node.group_next while next do name = name .. "/" .. next.name next = next.group_next end if not has_children then folder_hl = "NvimTreeEmptyFolderName" end if node.open then folder_hl = "NvimTreeOpenedFolderName" end if special[node.absolute_path] then folder_hl = "NvimTreeSpecialFolderName" end set_folder_hl(index, offset, #icon + #git_icon, #name, folder_hl) if git_hl then set_folder_hl(index, offset, #icon + #git_icon, #name, git_hl) end index = index + 1 if node.open then table.insert(lines, padding .. icon .. git_icon .. name .. (vim.g.nvim_tree_add_trailing == 1 and "/" or "")) update_draw_data(node, depth + 2, markers) else table.insert(lines, padding .. icon .. git_icon .. name .. (vim.g.nvim_tree_add_trailing == 1 and "/" or "")) end elseif node.link_to then local icon = get_symlink_icon() local link_hl = git_hl or "NvimTreeSymlink" local arrow = vim.g.nvim_tree_symlink_arrow or " ➛ " table.insert(hl, { link_hl, index, offset, -1 }) table.insert(lines, padding .. icon .. node.name .. arrow .. node.link_to) index = index + 1 else local icon local git_icons if special[node.absolute_path] or special[node.name] then icon = get_special_icon() git_icons = get_git_icons(node, index, offset, 0) table.insert(hl, { "NvimTreeSpecialFile", index, offset + #git_icons, -1 }) else icon = get_file_icon(node.name, node.extension, index, offset) git_icons = get_git_icons(node, index, offset, #icon) end table.insert(lines, padding .. icon .. git_icons .. node.name) if node.executable then table.insert(hl, { "NvimTreeExecFile", index, offset + #icon + #git_icons, -1 }) elseif picture[node.extension] then table.insert(hl, { "NvimTreeImageFile", index, offset + #icon + #git_icons, -1 }) end if should_hl_opened_files then if vim.fn.bufloaded(node.absolute_path) > 0 then if vim.g.nvim_tree_highlight_opened_files == 1 then table.insert(hl, { "NvimTreeOpenedFile", index, offset, offset + #icon }) -- highlight icon only elseif vim.g.nvim_tree_highlight_opened_files == 2 then table.insert(hl, { "NvimTreeOpenedFile", index, offset + #icon + #git_icons, offset + #icon + #git_icons + #node.name, }) -- highlight name only elseif vim.g.nvim_tree_highlight_opened_files == 3 then table.insert(hl, { "NvimTreeOpenedFile", index, offset, -1 }) -- highlight whole line end end end if git_hl then table.insert(hl, { git_hl, index, offset + #icon + #git_icons, -1 }) end index = index + 1 end end end local M = {} function M.draw() local bufnr = view.get_bufnr() if not TreeExplorer or not bufnr or not api.nvim_buf_is_loaded(bufnr) then return end local cursor if view.is_visible() then cursor = api.nvim_win_get_cursor(view.get_winnr()) end index = 0 lines = {} hl = {} icon_state = _icons.get_config() local show_arrows = vim.g.nvim_tree_indent_markers ~= 1 and icon_state.show_folder_icon and icon_state.show_folder_arrows _padding.reload_padding_function() update_draw_data(TreeExplorer, show_arrows and 2 or 0, {}) if view.is_help_ui() then lines, hl = _help.compute_lines() end api.nvim_buf_set_option(bufnr, "modifiable", true) api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) M.render_hl(bufnr) api.nvim_buf_set_option(bufnr, "modifiable", false) if cursor and #lines >= cursor[1] then api.nvim_win_set_cursor(view.get_winnr(), cursor) end end function M.render_hl(bufnr) if not bufnr or not api.nvim_buf_is_loaded(bufnr) then return end api.nvim_buf_clear_namespace(bufnr, namespace_id, 0, -1) for _, data in ipairs(hl) do api.nvim_buf_add_highlight(bufnr, namespace_id, data[1], data[2], data[3], data[4]) end end return M