nvim-tree.lua/lua/nvim-tree/renderer/decorator/git.lua
Alexander Courtis 610a1c189b
chore: resolve undefined-field warnings, fix link git statuses, rewrite devicons (#2968)
* add todo

* refactor(#2886): multi instance: node class refactoring: extract links, *_git_status (#2944)

* extract DirectoryLinkNode and FileLinkNode, move Node methods to children

* temporarily move DirectoryNode methods into BaseNode for easier reviewing

* move mostly unchanged DirectoryNode methods back to BaseNode

* tidy

* git.git_status_file takes an array

* update git status of links

* luacheck hack

* safer git_status_dir

* refactor(#2886): multi instance: node class refactoring: DirectoryNode:expand_or_collapse (#2957)

move expand_or_collapse to DirectoryNode

* refactor(#2886): multi instance: node group functions refactoring (#2959)

* move last_group_node to DirectoryNode

* move add BaseNode:as and more doc

* revert parameter name changes

* revert parameter name changes

* add Class

* move group methods into DN

* tidy group methods

* tidy group methods

* tidy group methods

* tidy group methods

* parent is DirectoryNode

* tidy expand all

* BaseNode -> Node

* move watcher to DirectoryNode

* last_group_node is DirectoryNode only

* simplify create-file

* simplify parent

* simplify collapse-all

* simplify live-filter

* style

* move lib.get_cursor_position to Explorer

* move lib.get_node_at_cursor to Explorer

* move lib.get_nodes to Explorer

* move place_cursor_on_node to Explorer

* resolve resource leak in purge_all_state

* move many autocommands into Explorer

* post merge tidy

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* Revert "chore: resolve undefined-field"

This reverts commit be546ff18d41f28466b065c857e1e041659bd2c8.

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* Revert "chore: resolve undefined-field"

This reverts commit e82db1c44d.

* chore: resolve undefined-field

* chore: class new is now generic

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* Revert "chore: resolve undefined-field"

This reverts commit 0e9b844d22.

* move icon builders into node classes

* move icon builders into node classes

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* chore: resolve undefined-field

* move folder specifics from icons to Directory

* move folder specifics from icons to Directory

* move folder specifics from icons to Directory

* move folder specifics from icons to Directory

* move file specifics from icons to File

* clean up sorters

* chore: resolve undefined-field

* tidy hl icon name

* file devicon uses library to fall back

* file devicon uses library to fall back

* file devicon uses library to fall back
2024-11-03 14:06:12 +11:00

231 lines
6.9 KiB
Lua

local notify = require("nvim-tree.notify")
local HL_POSITION = require("nvim-tree.enum").HL_POSITION
local ICON_PLACEMENT = require("nvim-tree.enum").ICON_PLACEMENT
local Decorator = require("nvim-tree.renderer.decorator")
local DirectoryNode = require("nvim-tree.node.directory")
---@class (exact) GitHighlightedString: HighlightedString
---@field ord number decreasing priority
---@alias GitStatusStrings "deleted" | "ignored" | "renamed" | "staged" | "unmerged" | "unstaged" | "untracked"
---@alias GitIconsByStatus table<GitStatusStrings, GitHighlightedString> human status
---@alias GitIconsByXY table<GitXY, GitHighlightedString[]> porcelain status
---@alias GitGlyphsByStatus table<GitStatusStrings, string> from opts
---@class (exact) DecoratorGit: Decorator
---@field file_hl_by_xy table<GitXY, string>?
---@field folder_hl_by_xy table<GitXY, string>?
---@field icons_by_status GitIconsByStatus?
---@field icons_by_xy GitIconsByXY?
local DecoratorGit = Decorator:new()
---Static factory method
---@param opts table
---@param explorer Explorer
---@return DecoratorGit
function DecoratorGit:create(opts, explorer)
---@type DecoratorGit
local o = {
explorer = explorer,
enabled = opts.git.enable,
hl_pos = HL_POSITION[opts.renderer.highlight_git] or HL_POSITION.none,
icon_placement = ICON_PLACEMENT[opts.renderer.icons.git_placement] or ICON_PLACEMENT.none,
}
o = self:new(o)
if not o.enabled then
return o
end
if o.hl_pos ~= HL_POSITION.none then
o:build_file_folder_hl_by_xy()
end
if opts.renderer.icons.show.git then
o:build_icons_by_status(opts.renderer.icons.glyphs.git)
o:build_icons_by_xy(o.icons_by_status)
for _, icon in pairs(o.icons_by_status) do
self:define_sign(icon)
end
end
return o
end
---@param glyphs GitGlyphsByStatus
function DecoratorGit:build_icons_by_status(glyphs)
self.icons_by_status = {}
self.icons_by_status.staged = { str = glyphs.staged, hl = { "NvimTreeGitStagedIcon" }, ord = 1 }
self.icons_by_status.unstaged = { str = glyphs.unstaged, hl = { "NvimTreeGitDirtyIcon" }, ord = 2 }
self.icons_by_status.renamed = { str = glyphs.renamed, hl = { "NvimTreeGitRenamedIcon" }, ord = 3 }
self.icons_by_status.deleted = { str = glyphs.deleted, hl = { "NvimTreeGitDeletedIcon" }, ord = 4 }
self.icons_by_status.unmerged = { str = glyphs.unmerged, hl = { "NvimTreeGitMergeIcon" }, ord = 5 }
self.icons_by_status.untracked = { str = glyphs.untracked, hl = { "NvimTreeGitNewIcon" }, ord = 6 }
self.icons_by_status.ignored = { str = glyphs.ignored, hl = { "NvimTreeGitIgnoredIcon" }, ord = 7 }
end
---@param icons GitIconsByXY
function DecoratorGit:build_icons_by_xy(icons)
self.icons_by_xy = {
["M "] = { icons.staged },
[" M"] = { icons.unstaged },
["C "] = { icons.staged },
[" C"] = { icons.unstaged },
["CM"] = { icons.unstaged },
[" T"] = { icons.unstaged },
["T "] = { icons.staged },
["TM"] = { icons.staged, icons.unstaged },
["MM"] = { icons.staged, icons.unstaged },
["MD"] = { icons.staged },
["A "] = { icons.staged },
["AD"] = { icons.staged },
[" A"] = { icons.untracked },
-- not sure about this one
["AA"] = { icons.unmerged, icons.untracked },
["AU"] = { icons.unmerged, icons.untracked },
["AM"] = { icons.staged, icons.unstaged },
["??"] = { icons.untracked },
["R "] = { icons.renamed },
[" R"] = { icons.renamed },
["RM"] = { icons.unstaged, icons.renamed },
["UU"] = { icons.unmerged },
["UD"] = { icons.unmerged },
["UA"] = { icons.unmerged },
[" D"] = { icons.deleted },
["D "] = { icons.deleted },
["DA"] = { icons.unstaged },
["RD"] = { icons.deleted },
["DD"] = { icons.deleted },
["DU"] = { icons.deleted, icons.unmerged },
["!!"] = { icons.ignored },
dirty = { icons.unstaged },
}
end
function DecoratorGit:build_file_folder_hl_by_xy()
self.file_hl_by_xy = {
["M "] = "NvimTreeGitFileStagedHL",
["C "] = "NvimTreeGitFileStagedHL",
["AA"] = "NvimTreeGitFileStagedHL",
["AD"] = "NvimTreeGitFileStagedHL",
["MD"] = "NvimTreeGitFileStagedHL",
["T "] = "NvimTreeGitFileStagedHL",
["TT"] = "NvimTreeGitFileStagedHL",
[" M"] = "NvimTreeGitFileDirtyHL",
["CM"] = "NvimTreeGitFileDirtyHL",
[" C"] = "NvimTreeGitFileDirtyHL",
[" T"] = "NvimTreeGitFileDirtyHL",
["MM"] = "NvimTreeGitFileDirtyHL",
["AM"] = "NvimTreeGitFileDirtyHL",
dirty = "NvimTreeGitFileDirtyHL",
["A "] = "NvimTreeGitFileStagedHL",
["??"] = "NvimTreeGitFileNewHL",
["AU"] = "NvimTreeGitFileMergeHL",
["UU"] = "NvimTreeGitFileMergeHL",
["UD"] = "NvimTreeGitFileMergeHL",
["DU"] = "NvimTreeGitFileMergeHL",
["UA"] = "NvimTreeGitFileMergeHL",
[" D"] = "NvimTreeGitFileDeletedHL",
["DD"] = "NvimTreeGitFileDeletedHL",
["RD"] = "NvimTreeGitFileDeletedHL",
["D "] = "NvimTreeGitFileDeletedHL",
["R "] = "NvimTreeGitFileRenamedHL",
["RM"] = "NvimTreeGitFileRenamedHL",
[" R"] = "NvimTreeGitFileRenamedHL",
["!!"] = "NvimTreeGitFileIgnoredHL",
[" A"] = "none",
}
self.folder_hl_by_xy = {}
for k, v in pairs(self.file_hl_by_xy) do
self.folder_hl_by_xy[k] = v:gsub("File", "Folder")
end
end
---Git icons: git.enable, renderer.icons.show.git and node has status
---@param node Node
---@return HighlightedString[]|nil modified icon
function DecoratorGit:calculate_icons(node)
if not node or not self.enabled or not self.icons_by_xy then
return nil
end
local git_xy = node:get_git_xy()
if git_xy == nil then
return nil
end
local inserted = {}
local iconss = {}
for _, s in pairs(git_xy) do
local icons = self.icons_by_xy[s]
if not icons then
if self.hl_pos == HL_POSITION.none then
notify.warn(string.format("Unrecognized git state '%s'", git_xy))
end
return nil
end
for _, icon in pairs(icons) do
if #icon.str > 0 then
if not inserted[icon] then
table.insert(iconss, icon)
inserted[icon] = true
end
end
end
end
if #iconss == 0 then
return nil
end
-- sort icons so it looks slightly better
table.sort(iconss, function(a, b)
return a.ord < b.ord
end)
return iconss
end
---Get the first icon as the sign if appropriate
---@param node Node
---@return string|nil name
function DecoratorGit:sign_name(node)
if self.icon_placement ~= ICON_PLACEMENT.signcolumn then
return
end
local icons = self:calculate_icons(node)
if icons and #icons > 0 then
return icons[1].hl[1]
end
end
---Git highlight: git.enable, renderer.highlight_git and node has status
---@param node Node
---@return string|nil group
function DecoratorGit:calculate_highlight(node)
if not node or not self.enabled or self.hl_pos == HL_POSITION.none then
return nil
end
local git_xy = node:get_git_xy()
if not git_xy then
return nil
end
if node:is(DirectoryNode) then
return self.folder_hl_by_xy[git_xy[1]]
else
return self.file_hl_by_xy[git_xy[1]]
end
end
return DecoratorGit