summaryrefslogtreecommitdiffstats
path: root/lua/netrw
diff options
context:
space:
mode:
authorFasamii <fasami0512@gmail.com>2026-02-05 18:53:56 +0100
committerFasamii <fasami0512@gmail.com>2026-02-05 18:53:56 +0100
commit6e17aeb0c1f26fcfac9c5a1bfd24234005da2bd3 (patch)
treeb6c129f981c65784b79bb0c5d2ab50e0db21761e /lua/netrw
parente3c8011d767532e965946733fa8a5c70d5b8a082 (diff)
downloadnetrw-icons.nvim-6e17aeb0c1f26fcfac9c5a1bfd24234005da2bd3.tar.gz
netrw-icons.nvim-6e17aeb0c1f26fcfac9c5a1bfd24234005da2bd3.tar.bz2
netrw-icons.nvim-6e17aeb0c1f26fcfac9c5a1bfd24234005da2bd3.tar.xz
netrw-icons.nvim-6e17aeb0c1f26fcfac9c5a1bfd24234005da2bd3.tar.zst
main functionality
Diffstat (limited to 'lua/netrw')
-rw-r--r--lua/netrw/init.lua149
-rw-r--r--lua/netrw/parse.lua185
2 files changed, 334 insertions, 0 deletions
diff --git a/lua/netrw/init.lua b/lua/netrw/init.lua
index e69de29..379d5a1 100644
--- a/lua/netrw/init.lua
+++ b/lua/netrw/init.lua
@@ -0,0 +1,149 @@
+local M = {}
+local parse = require("netrw.parse")
+
+local icon_provider = nil
+
+local function get_icon_provider(prefer)
+ local providers = {}
+
+ local has_devicons, devicons = pcall(require, "nvim-web-devicons")
+ if has_devicons then
+ providers.devicons = devicons
+ end
+
+ local has_miniicons, miniicons = pcall(require, "mini.icons")
+ if has_miniicons then
+ providers.miniicons = miniicons
+ end
+
+ if prefer and providers[prefer] then
+ return { type = prefer, provider = providers[prefer] }
+ end
+
+ if providers.miniicons then
+ return { type = "miniicons", provider = providers.miniicons }
+ elseif providers.devicons then
+ return { type = "devicons", provider = providers.devicons }
+ end
+
+ return nil
+end
+
+local function get_file_icon(node)
+ if not icon_provider then
+ return nil, nil
+ end
+
+ if icon_provider.type == "devicons" then
+ local icon, hl = icon_provider.provider.get_icon(node.node, nil, { strict = true, default = false })
+ return icon, hl
+ elseif icon_provider.type == "miniicons" then
+ local icon, hl = icon_provider.provider.get("file", node.node)
+ return icon, hl
+ end
+
+ return nil, nil
+end
+
+local function get_lsp_diagnostics(bufnr, node)
+ local diagnostics = {}
+ local filepath = node.dir .. "/" .. node.node
+
+ local diag = vim.diagnostic.get(nil, { path = filepath })
+
+ for _, d in ipairs(diag) do
+ if d.severity == vim.diagnostic.severity.ERROR and M.options.lsp.error then
+ table.insert(diagnostics, { icon = "E", hl = "DiagnosticError" })
+ elseif d.severity == vim.diagnostic.severity.WARN and M.options.lsp.warn then
+ table.insert(diagnostics, { icon = "W", hl = "DiagnosticWarn" })
+ elseif d.severity == vim.diagnostic.severity.INFO and M.options.lsp.info then
+ table.insert(diagnostics, { icon = "I", hl = "DiagnosticInfo" })
+ elseif d.severity == vim.diagnostic.severity.HINT and M.options.lsp.hint then
+ table.insert(diagnostics, { icon = "H", hl = "DiagnosticHint" })
+ end
+ end
+
+ return diagnostics
+end
+
+local function draw(bufnr)
+ local namespace = vim.api.nvim_create_namespace("netrw")
+ vim.api.nvim_buf_clear_namespace(bufnr, namespace, 0, -1)
+
+ local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
+
+ for i, line in ipairs(lines) do
+ local node = parse.get_node(line)
+ if node then
+ -- Draw file/dir icon on the left
+ if (node.type == parse.TYPE_FILE and M.options.file) or
+ (node.type == parse.TYPE_DIR and M.options.dir) then
+ local icon, hl = get_file_icon(node)
+ if icon then
+ local opts = {
+ id = i * 2, -- Unique ID for icon
+ virt_text = hl and { { icon .. " ", hl } } or { { icon .. " " } },
+ virt_text_pos = "inline",
+ }
+ vim.api.nvim_buf_set_extmark(bufnr, namespace, i - 1, node.icon_col, opts)
+ end
+ end
+
+ -- Draw LSP diagnostics on the right
+ if node.type == parse.TYPE_FILE then
+ local diagnostics = get_lsp_diagnostics(bufnr, node)
+ if #diagnostics > 0 then
+ local virt_text = {}
+ for _, diag in ipairs(diagnostics) do
+ table.insert(virt_text, { " " .. diag.icon, diag.hl })
+ end
+
+ local opts = {
+ id = i * 2 + 1, -- Unique ID for LSP
+ virt_text = virt_text,
+ virt_text_pos = "inline",
+ }
+ vim.api.nvim_buf_set_extmark(bufnr, namespace, i - 1, node.lsp_col, opts)
+ end
+ end
+ end
+ end
+end
+
+--- @class Config
+M.options = {}
+
+--- @class Config
+local default = {
+ prefer = nil, -- "miniicons" or "devicons"
+ file = true,
+ dir = true,
+ lsp = {
+ info = false,
+ hint = false,
+ warn = true,
+ error = true,
+ }
+}
+
+---@param options Config|nil
+function M.setup(options)
+ M.options = vim.tbl_deep_extend("force", {}, default, options or {})
+
+ if M.options.file or M.options.dir then
+ icon_provider = get_icon_provider(M.options.prefer)
+ end
+
+ vim.api.nvim_create_autocmd({ "BufEnter", "DiagnosticChanged" }, {
+ pattern = "*",
+ group = vim.api.nvim_create_augroup("netrw_icons", { clear = true }),
+ callback = function(args)
+ if vim.bo[args.buf].filetype ~= "netrw" then
+ return
+ end
+ draw(args.buf)
+ end
+ })
+end
+
+return M
diff --git a/lua/netrw/parse.lua b/lua/netrw/parse.lua
new file mode 100644
index 0000000..c069dee
--- /dev/null
+++ b/lua/netrw/parse.lua
@@ -0,0 +1,185 @@
+local M = {}
+
+M.TYPE_DIR = 0
+M.TYPE_FILE = 1
+M.TYPE_SYMLINK = 2
+
+---@alias Word {dir:string, node:string, link:string|nil, extension:string|nil, type:number, icon_col:number, name_col:number, lsp_col:number}
+
+---@param line string
+---@param curdir string
+---@return Word|nil
+local parse_liststyle_0 = function(line, curdir)
+ local _, _, node, link = string.find(line, "^(.+)@\t%s*%-%->%s*(.+)")
+ if node then
+ return {
+ dir = curdir,
+ icon_col = 0,
+ name_col = 0,
+ lsp_col = #node,
+ node = node,
+ extension = vim.fn.fnamemodify(node, ":e"),
+ link = link,
+ type = M.TYPE_SYMLINK,
+ }
+ end
+
+ local _, _, dir = string.find(line, "^(.*)/")
+ if dir then
+ return {
+ dir = curdir,
+ icon_col = 0,
+ name_col = 0,
+ lsp_col = #dir + 1, -- +1 for the "/"
+ node = dir,
+ type = M.TYPE_DIR,
+ }
+ end
+
+ local ext = vim.fn.fnamemodify(line, ":e")
+ local clean_line = line
+ if string.sub(ext, -1) == "*" then
+ ext = string.sub(ext, 1, -2)
+ clean_line = string.sub(line, 1, -2)
+ end
+
+ return {
+ dir = curdir,
+ icon_col = 0,
+ name_col = 0,
+ lsp_col = #clean_line,
+ node = clean_line,
+ extension = ext,
+ type = M.TYPE_FILE,
+ }
+end
+
+---@param line string
+---@param curdir string
+---@return Word|nil
+local parse_liststyle_1 = function(line, curdir)
+ local _, _, node, link = string.find(line, "^(.+)@%s+")
+ if node then
+ return {
+ dir = curdir,
+ icon_col = 0,
+ name_col = 0,
+ lsp_col = #node,
+ node = node,
+ extension = vim.fn.fnamemodify(node, ":e"),
+ link = link,
+ type = M.TYPE_SYMLINK,
+ }
+ end
+
+ local _, _, dir = string.find(line, "^(.*)/")
+ if dir then
+ return {
+ dir = curdir,
+ icon_col = 0,
+ name_col = 0,
+ lsp_col = #dir + 1,
+ node = dir,
+ type = M.TYPE_DIR,
+ }
+ end
+
+ local file = vim.fn.substitute(line, "^\\(\\%(\\S\\+ \\)*\\S\\+\\).\\{-}$", "\\1", "e")
+ local ext = vim.fn.fnamemodify(file, ":e")
+ if string.sub(ext, -1) == "*" then
+ ext = string.sub(ext, 1, -2)
+ file = string.sub(file, 1, -2)
+ end
+
+ return {
+ dir = curdir,
+ icon_col = 0,
+ name_col = 0,
+ lsp_col = #file,
+ node = file,
+ extension = ext,
+ type = M.TYPE_FILE,
+ }
+end
+
+---@param line string
+---@return Word|nil
+local parse_liststyle_3 = function(line)
+ local curdir = vim.b.netrw_curdir
+ local _, to = string.find(line, "^[|%s]*")
+ local pipelessLine = string.sub(line, to + 1, #line)
+
+ if pipelessLine == "" then
+ return nil
+ end
+
+ local _, _, node, link = string.find(pipelessLine, "^(.+)@\t%s*%-%->%s*(.+)")
+ if node then
+ return {
+ dir = curdir,
+ icon_col = to,
+ name_col = to,
+ lsp_col = to + #node,
+ node = node,
+ extension = vim.fn.fnamemodify(node, ":e"),
+ link = link,
+ type = M.TYPE_SYMLINK,
+ }
+ end
+
+ local _, _, dir = string.find(pipelessLine, "^(.*)/")
+ if dir then
+ return {
+ dir = curdir,
+ icon_col = to,
+ name_col = to,
+ lsp_col = to + #dir + 1,
+ node = dir,
+ type = M.TYPE_DIR,
+ }
+ end
+
+ local ext = vim.fn.fnamemodify(pipelessLine, ":e")
+ local clean_line = pipelessLine
+ if string.sub(ext, -1) == "*" then
+ ext = string.sub(ext, 1, -2)
+ clean_line = string.sub(pipelessLine, 1, -2)
+ end
+
+ return {
+ dir = curdir,
+ icon_col = to,
+ name_col = to,
+ lsp_col = to + #clean_line,
+ node = clean_line,
+ extension = ext,
+ type = M.TYPE_FILE,
+ }
+end
+
+---@param line string
+---@return Word|nil
+M.get_node = function(line)
+ if string.find(line, '^"') then
+ return nil
+ end
+
+ if line == "" then
+ return nil
+ end
+
+ local curdir = vim.b.netrw_curdir
+ local liststyle = vim.b.netrw_liststyle
+
+ if liststyle == 0 then
+ return parse_liststyle_0(line, curdir)
+ elseif liststyle == 1 then
+ return parse_liststyle_1(line, curdir)
+ elseif liststyle == 3 then
+ return parse_liststyle_3(line)
+ end
+
+ return nil
+end
+
+return M