diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml index cd08e6e..2548134 100644 --- a/.github/workflows/smoke.yml +++ b/.github/workflows/smoke.yml @@ -21,6 +21,12 @@ jobs: go install mvdan.cc/sh/v3/cmd/shfmt@v3.13.1 echo "$HOME/go/bin" >> "$GITHUB_PATH" + - name: Install stylua + uses: JohnnyMorganz/stylua-action@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + version: latest + - name: Check shell formatting run: make shell-format-check @@ -33,6 +39,9 @@ jobs: - name: Lint TOML configs run: make toml-lint + - name: Check Lua formatting with stylua + run: make stylua-check + - name: Check zsh config run: make zsh-check diff --git a/Brewfile b/Brewfile index bb9d4a1..2858ce5 100644 --- a/Brewfile +++ b/Brewfile @@ -51,6 +51,7 @@ brew "rust" # Systems programming language brew "sd" # Better sed brew "shellcheck" # Shell script linter brew "shfmt" # Shell script formatter +brew "stylua" # Lua formatter (for nvim/ and yazi/ configs) brew "tmux" # Terminal multiplexer brew "tokei" # Code statistics brew "wget" # File downloader diff --git a/Makefile b/Makefile index 6f5f130..9909749 100644 --- a/Makefile +++ b/Makefile @@ -9,11 +9,15 @@ SHELLCHECK_FILES := .bash_profile .bashrc bin/brew-sync bin/cheatsheet bin/color TOML_FILES := $(shell find . -path './.git' -prune -o -path './yazi/flavors' -prune -o -path './yazi/plugins' -prune -o -type f -name '*.toml' -print | sort | sed 's,^\./,,') ZSH_FILES := .zshrc chetmancini.zsh-theme forge-zsh.sh linux_specific.sh mac_specific.sh -.PHONY: format check shell-format shell-format-check shell-syntax shellcheck toml-lint zsh-check +STYLUA ?= stylua +STYLUA_CONFIG := nvim/stylua.toml +STYLUA_FILES := $(shell find . -path './.git' -prune -o -path './yazi/flavors' -prune -o -name '*.lua' -type f -print | sort | sed 's,^\./,,') -format: shell-format +.PHONY: format check shell-format shell-format-check shell-syntax shellcheck toml-lint zsh-check stylua-check stylua-format -check: shell-format-check shell-syntax shellcheck toml-lint zsh-check +format: shell-format stylua-format + +check: shell-format-check shell-syntax shellcheck toml-lint zsh-check stylua-check shell-format: $(SHFMT) $(SHFMT_FLAGS) -w $(SHFMT_FILES) @@ -34,3 +38,9 @@ toml-lint: zsh-check: zsh -n $(ZSH_FILES) + +stylua-check: + $(STYLUA) --config-path $(STYLUA_CONFIG) --check $(STYLUA_FILES) + +stylua-format: + $(STYLUA) --config-path $(STYLUA_CONFIG) $(STYLUA_FILES) diff --git a/README.md b/README.md index b126595..608cd5e 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ brew bundle cleanup --file=~/dotfiles/Brewfile - `install.sh` supports interactive, preview, and headless installs (`--plan`, `--yes`, `--skip-brew`, etc.) - `doctor` verifies symlinks, theme wiring, TPM, and repo health checks - GitHub Actions smoke-tests the installer and doctor in a temporary `HOME` -- `make format` formats shell scripts with `shfmt`; `make check` runs formatting, syntax, ShellCheck, TOML, and zsh checks +- `make format` formats shell scripts with `shfmt` and Lua files with `stylua`; `make check` runs formatting, syntax, ShellCheck, TOML, stylua, and zsh checks ### Git - Conventional commit aliases: `git feat`, `git fix`, `git chore`, etc. diff --git a/nvim/plugin/git.lua b/nvim/plugin/git.lua index 6191232..9b7a0a3 100644 --- a/nvim/plugin/git.lua +++ b/nvim/plugin/git.lua @@ -4,16 +4,26 @@ require("gitsigns").setup({ local map = function(mode, lhs, rhs, desc) vim.keymap.set(mode, lhs, rhs, { buffer = buffer, desc = desc }) end - map("n", "]h", function() gs.nav_hunk("next") end, "Next Hunk") - map("n", "[h", function() gs.nav_hunk("prev") end, "Prev Hunk") + map("n", "]h", function() + gs.nav_hunk("next") + end, "Next Hunk") + map("n", "[h", function() + gs.nav_hunk("prev") + end, "Prev Hunk") map("n", "ghs", gs.stage_hunk, "Stage Hunk") map("n", "ghr", gs.reset_hunk, "Reset Hunk") - map("v", "ghs", function() gs.stage_hunk({ vim.fn.line("."), vim.fn.line("v") }) end, "Stage Hunk") - map("v", "ghr", function() gs.reset_hunk({ vim.fn.line("."), vim.fn.line("v") }) end, "Reset Hunk") + map("v", "ghs", function() + gs.stage_hunk({ vim.fn.line("."), vim.fn.line("v") }) + end, "Stage Hunk") + map("v", "ghr", function() + gs.reset_hunk({ vim.fn.line("."), vim.fn.line("v") }) + end, "Reset Hunk") map("n", "ghS", gs.stage_buffer, "Stage Buffer") map("n", "ghR", gs.reset_buffer, "Reset Buffer") map("n", "ghp", gs.preview_hunk_inline, "Preview Hunk") - map("n", "ghb", function() gs.blame_line({ full = true }) end, "Blame Line") + map("n", "ghb", function() + gs.blame_line({ full = true }) + end, "Blame Line") map("n", "ghd", gs.diffthis, "Diff This") end, }) diff --git a/nvim/plugin/treesitter.lua b/nvim/plugin/treesitter.lua index 1ad6d8b..82c052c 100644 --- a/nvim/plugin/treesitter.lua +++ b/nvim/plugin/treesitter.lua @@ -2,10 +2,33 @@ local ts = require("nvim-treesitter") local desired_parsers = { - "bash", "c", "css", "diff", "html", "javascript", "jsdoc", "json", "jsonc", - "lua", "luadoc", "luap", "markdown", "markdown_inline", "printf", "python", - "query", "regex", "rust", "sql", "toml", "tsx", "typescript", "vim", - "vimdoc", "xml", "yaml", + "bash", + "c", + "css", + "diff", + "html", + "javascript", + "jsdoc", + "json", + "jsonc", + "lua", + "luadoc", + "luap", + "markdown", + "markdown_inline", + "printf", + "python", + "query", + "regex", + "rust", + "sql", + "toml", + "tsx", + "typescript", + "vim", + "vimdoc", + "xml", + "yaml", } vim.api.nvim_create_autocmd("VimEnter", { @@ -27,24 +50,56 @@ require("nvim-treesitter-textobjects").setup() local move = require("nvim-treesitter-textobjects.move") local map = vim.keymap.set -map({ "n", "x", "o" }, "]f", function() move.goto_next_start("@function.outer") end, { desc = "Next Function Start" }) -map({ "n", "x", "o" }, "]F", function() move.goto_next_end("@function.outer") end, { desc = "Next Function End" }) -map({ "n", "x", "o" }, "]c", function() move.goto_next_start("@class.outer") end, { desc = "Next Class Start" }) -map({ "n", "x", "o" }, "]C", function() move.goto_next_end("@class.outer") end, { desc = "Next Class End" }) -map({ "n", "x", "o" }, "]a", function() move.goto_next_start("@parameter.inner") end, { desc = "Next Parameter" }) -map({ "n", "x", "o" }, "[f", function() move.goto_previous_start("@function.outer") end, { desc = "Prev Function Start" }) -map({ "n", "x", "o" }, "[F", function() move.goto_previous_end("@function.outer") end, { desc = "Prev Function End" }) -map({ "n", "x", "o" }, "[c", function() move.goto_previous_start("@class.outer") end, { desc = "Prev Class Start" }) -map({ "n", "x", "o" }, "[C", function() move.goto_previous_end("@class.outer") end, { desc = "Prev Class End" }) -map({ "n", "x", "o" }, "[a", function() move.goto_previous_start("@parameter.inner") end, { desc = "Prev Parameter" }) +map({ "n", "x", "o" }, "]f", function() + move.goto_next_start("@function.outer") +end, { desc = "Next Function Start" }) +map({ "n", "x", "o" }, "]F", function() + move.goto_next_end("@function.outer") +end, { desc = "Next Function End" }) +map({ "n", "x", "o" }, "]c", function() + move.goto_next_start("@class.outer") +end, { desc = "Next Class Start" }) +map({ "n", "x", "o" }, "]C", function() + move.goto_next_end("@class.outer") +end, { desc = "Next Class End" }) +map({ "n", "x", "o" }, "]a", function() + move.goto_next_start("@parameter.inner") +end, { desc = "Next Parameter" }) +map({ "n", "x", "o" }, "[f", function() + move.goto_previous_start("@function.outer") +end, { desc = "Prev Function Start" }) +map({ "n", "x", "o" }, "[F", function() + move.goto_previous_end("@function.outer") +end, { desc = "Prev Function End" }) +map({ "n", "x", "o" }, "[c", function() + move.goto_previous_start("@class.outer") +end, { desc = "Prev Class Start" }) +map({ "n", "x", "o" }, "[C", function() + move.goto_previous_end("@class.outer") +end, { desc = "Prev Class End" }) +map({ "n", "x", "o" }, "[a", function() + move.goto_previous_start("@parameter.inner") +end, { desc = "Prev Parameter" }) local select = require("nvim-treesitter-textobjects.select") -map({ "x", "o" }, "af", function() select.select_textobject("@function.outer") end, { desc = "outer function" }) -map({ "x", "o" }, "if", function() select.select_textobject("@function.inner") end, { desc = "inner function" }) -map({ "x", "o" }, "ac", function() select.select_textobject("@class.outer") end, { desc = "outer class" }) -map({ "x", "o" }, "ic", function() select.select_textobject("@class.inner") end, { desc = "inner class" }) -map({ "x", "o" }, "aa", function() select.select_textobject("@parameter.outer") end, { desc = "outer parameter" }) -map({ "x", "o" }, "ia", function() select.select_textobject("@parameter.inner") end, { desc = "inner parameter" }) +map({ "x", "o" }, "af", function() + select.select_textobject("@function.outer") +end, { desc = "outer function" }) +map({ "x", "o" }, "if", function() + select.select_textobject("@function.inner") +end, { desc = "inner function" }) +map({ "x", "o" }, "ac", function() + select.select_textobject("@class.outer") +end, { desc = "outer class" }) +map({ "x", "o" }, "ic", function() + select.select_textobject("@class.inner") +end, { desc = "inner class" }) +map({ "x", "o" }, "aa", function() + select.select_textobject("@parameter.outer") +end, { desc = "outer parameter" }) +map({ "x", "o" }, "ia", function() + select.select_textobject("@parameter.inner") +end, { desc = "inner parameter" }) -- Autotag and comments require("nvim-ts-autotag").setup() diff --git a/yazi/init.lua b/yazi/init.lua index 8ada53d..eb2b412 100644 --- a/yazi/init.lua +++ b/yazi/init.lua @@ -1,23 +1,20 @@ require("git"):setup() - -require("full-border"):setup { - -- Available values: ui.Border.PLAIN, ui.Border.ROUNDED - type = ui.Border.ROUNDED, -} - +require("full-border"):setup({ + -- Available values: ui.Border.PLAIN, ui.Border.ROUNDED + type = ui.Border.ROUNDED, +}) function Linemode:size_and_mtime() - local time = math.floor(self._file.cha.mtime or 0) - if time == 0 then - time = "" - elseif os.date("%Y", time) == os.date("%Y") then - time = os.date("%b %d %H:%M", time) - else - time = os.date("%b %d %Y", time) - end + local time = math.floor(self._file.cha.mtime or 0) + if time == 0 then + time = "" + elseif os.date("%Y", time) == os.date("%Y") then + time = os.date("%b %d %H:%M", time) + else + time = os.date("%b %d %Y", time) + end - local size = self._file:size() - return string.format("%s %s", size and ya.readable_size(size) or "-", time) + local size = self._file:size() + return string.format("%s %s", size and ya.readable_size(size) or "-", time) end - diff --git a/yazi/plugins/full-border.yazi/main.lua b/yazi/plugins/full-border.yazi/main.lua index a917e1e..3fcbe5e 100644 --- a/yazi/plugins/full-border.yazi/main.lua +++ b/yazi/plugins/full-border.yazi/main.lua @@ -1,43 +1,43 @@ --- @since 25.2.26 local function setup(_, opts) - local type = opts and opts.type or ui.Border.ROUNDED - local old_build = Tab.build - - Tab.build = function(self, ...) - local bar = function(c, x, y) - if x <= 0 or x == self._area.w - 1 or th.mgr.border_symbol ~= "│" then - return ui.Bar(ui.Edge.TOP) - end - - return ui.Bar(ui.Edge.TOP) - :area( - ui.Rect { x = x, y = math.max(0, y), w = ya.clamp(0, self._area.w - x, 1), h = math.min(1, self._area.h) } - ) - :symbol(c) - end - - local c = self._chunks - self._chunks = { - c[1]:pad(ui.Pad.y(1)), - c[2]:pad(ui.Pad(1, c[3].w > 0 and 0 or 1, 1, c[1].w > 0 and 0 or 1)), - c[3]:pad(ui.Pad.y(1)), - } - - local style = th.mgr.border_style - self._base = ya.list_merge(self._base or {}, { - ui.Border(ui.Edge.ALL):area(self._area):type(type):style(style), - ui.Bar(ui.Edge.RIGHT):area(self._chunks[1]):style(style), - ui.Bar(ui.Edge.LEFT):area(self._chunks[3]):style(style), - - bar("┬", c[1].right - 1, c[1].y), - bar("┴", c[1].right - 1, c[1].bottom - 1), - bar("┬", c[2].right, c[2].y), - bar("┴", c[2].right, c[2].bottom - 1), - }) - - old_build(self, ...) - end + local type = opts and opts.type or ui.Border.ROUNDED + local old_build = Tab.build + + Tab.build = function(self, ...) + local bar = function(c, x, y) + if x <= 0 or x == self._area.w - 1 or th.mgr.border_symbol ~= "│" then + return ui.Bar(ui.Edge.TOP) + end + + return ui.Bar(ui.Edge.TOP) + :area( + ui.Rect({ x = x, y = math.max(0, y), w = ya.clamp(0, self._area.w - x, 1), h = math.min(1, self._area.h) }) + ) + :symbol(c) + end + + local c = self._chunks + self._chunks = { + c[1]:pad(ui.Pad.y(1)), + c[2]:pad(ui.Pad(1, c[3].w > 0 and 0 or 1, 1, c[1].w > 0 and 0 or 1)), + c[3]:pad(ui.Pad.y(1)), + } + + local style = th.mgr.border_style + self._base = ya.list_merge(self._base or {}, { + ui.Border(ui.Edge.ALL):area(self._area):type(type):style(style), + ui.Bar(ui.Edge.RIGHT):area(self._chunks[1]):style(style), + ui.Bar(ui.Edge.LEFT):area(self._chunks[3]):style(style), + + bar("┬", c[1].right - 1, c[1].y), + bar("┴", c[1].right - 1, c[1].bottom - 1), + bar("┬", c[2].right, c[2].y), + bar("┴", c[2].right, c[2].bottom - 1), + }) + + old_build(self, ...) + end end return { setup = setup } diff --git a/yazi/plugins/git.yazi/main.lua b/yazi/plugins/git.yazi/main.lua index cdc1532..736bac4 100644 --- a/yazi/plugins/git.yazi/main.lua +++ b/yazi/plugins/git.yazi/main.lua @@ -7,84 +7,84 @@ local WINDOWS = ya.target_family() == "windows" -- see `bubble_up` ---@enum CODES local CODES = { - excluded = 100, -- ignored directory - ignored = 6, -- ignored file - untracked = 5, - modified = 4, - added = 3, - deleted = 2, - updated = 1, - unknown = 0, + excluded = 100, -- ignored directory + ignored = 6, -- ignored file + untracked = 5, + modified = 4, + added = 3, + deleted = 2, + updated = 1, + unknown = 0, } local PATTERNS = { - { "!$", CODES.ignored }, - { "?$", CODES.untracked }, - { "[MT]", CODES.modified }, - { "[AC]", CODES.added }, - { "D", CODES.deleted }, - { "U", CODES.updated }, - { "[AD][AD]", CODES.updated }, + { "!$", CODES.ignored }, + { "?$", CODES.untracked }, + { "[MT]", CODES.modified }, + { "[AC]", CODES.added }, + { "D", CODES.deleted }, + { "U", CODES.updated }, + { "[AD][AD]", CODES.updated }, } ---@param line string ---@return CODES, string local function match(line) - local signs = line:sub(1, 2) - for _, p in ipairs(PATTERNS) do - local path, pattern, code = nil, p[1], p[2] - if signs:find(pattern) then - path = line:sub(4, 4) == '"' and line:sub(5, -2) or line:sub(4) - path = WINDOWS and path:gsub("/", "\\") or path - end - if not path then - elseif path:find("[/\\]$") then - -- Mark the ignored directory as `excluded`, so we can process it further within `propagate_down` - return code == CODES.ignored and CODES.excluded or code, path:sub(1, -2) - else - return code, path - end - ---@diagnostic disable-next-line: missing-return - end + local signs = line:sub(1, 2) + for _, p in ipairs(PATTERNS) do + local path, pattern, code = nil, p[1], p[2] + if signs:find(pattern) then + path = line:sub(4, 4) == '"' and line:sub(5, -2) or line:sub(4) + path = WINDOWS and path:gsub("/", "\\") or path + end + if not path then + elseif path:find("[/\\]$") then + -- Mark the ignored directory as `excluded`, so we can process it further within `propagate_down` + return code == CODES.ignored and CODES.excluded or code, path:sub(1, -2) + else + return code, path + end + ---@diagnostic disable-next-line: missing-return + end end ---@param cwd Url ---@return string? local function root(cwd) - local is_worktree = function(url) - local file, head = io.open(tostring(url)), nil - if file then - head = file:read(8) - file:close() - end - return head == "gitdir: " - end - - repeat - local next = cwd:join(".git") - local cha = fs.cha(next) - if cha and (cha.is_dir or is_worktree(next)) then - return tostring(cwd) - end - cwd = cwd.parent - until not cwd + local is_worktree = function(url) + local file, head = io.open(tostring(url)), nil + if file then + head = file:read(8) + file:close() + end + return head == "gitdir: " + end + + repeat + local next = cwd:join(".git") + local cha = fs.cha(next) + if cha and (cha.is_dir or is_worktree(next)) then + return tostring(cwd) + end + cwd = cwd.parent + until not cwd end ---@param changed Changes ---@return Changes local function bubble_up(changed) - local new, empty = {}, Url("") - for path, code in pairs(changed) do - if code ~= CODES.ignored then - local url = Url(path).parent - while url and url ~= empty do - local s = tostring(url) - new[s] = (new[s] or CODES.unknown) > code and new[s] or code - url = url.parent - end - end - end - return new + local new, empty = {}, Url("") + for path, code in pairs(changed) do + if code ~= CODES.ignored then + local url = Url(path).parent + while url and url ~= empty do + local s = tostring(url) + new[s] = (new[s] or CODES.unknown) > code and new[s] or code + url = url.parent + end + end + end + return new end ---@param excluded string[] @@ -92,127 +92,127 @@ end ---@param repo Url ---@return Changes local function propagate_down(excluded, cwd, repo) - local new, rel = {}, cwd:strip_prefix(repo) - for _, path in ipairs(excluded) do - if rel:starts_with(path) then - -- If `cwd` is a subdirectory of an excluded directory, also mark it as `excluded` - new[tostring(cwd)] = CODES.excluded - elseif cwd == repo:join(path).parent then - -- If `path` is a direct subdirectory of `cwd`, mark it as `ignored` - new[path] = CODES.ignored - else - -- Skipping, we only care about `cwd` itself and its direct subdirectories for maximum performance - end - end - return new + local new, rel = {}, cwd:strip_prefix(repo) + for _, path in ipairs(excluded) do + if rel:starts_with(path) then + -- If `cwd` is a subdirectory of an excluded directory, also mark it as `excluded` + new[tostring(cwd)] = CODES.excluded + elseif cwd == repo:join(path).parent then + -- If `path` is a direct subdirectory of `cwd`, mark it as `ignored` + new[path] = CODES.ignored + else + -- Skipping, we only care about `cwd` itself and its direct subdirectories for maximum performance + end + end + return new end ---@param cwd string ---@param repo string ---@param changed Changes local add = ya.sync(function(st, cwd, repo, changed) - ---@cast st State - - st.dirs[cwd] = repo - st.repos[repo] = st.repos[repo] or {} - for path, code in pairs(changed) do - if code == CODES.unknown then - st.repos[repo][path] = nil - elseif code == CODES.excluded then - -- Mark the directory with a special value `excluded` so that it can be distinguished during UI rendering - st.dirs[path] = CODES.excluded - else - st.repos[repo][path] = code - end - end - ui.render() + ---@cast st State + + st.dirs[cwd] = repo + st.repos[repo] = st.repos[repo] or {} + for path, code in pairs(changed) do + if code == CODES.unknown then + st.repos[repo][path] = nil + elseif code == CODES.excluded then + -- Mark the directory with a special value `excluded` so that it can be distinguished during UI rendering + st.dirs[path] = CODES.excluded + else + st.repos[repo][path] = code + end + end + ui.render() end) ---@param cwd string local remove = ya.sync(function(st, cwd) - ---@cast st State - - local repo = st.dirs[cwd] - if not repo then - return - end - - ui.render() - st.dirs[cwd] = nil - if not st.repos[repo] then - return - end - - for _, r in pairs(st.dirs) do - if r == repo then - return - end - end - st.repos[repo] = nil + ---@cast st State + + local repo = st.dirs[cwd] + if not repo then + return + end + + ui.render() + st.dirs[cwd] = nil + if not st.repos[repo] then + return + end + + for _, r in pairs(st.dirs) do + if r == repo then + return + end + end + st.repos[repo] = nil end) ---@param st State ---@param opts Options local function setup(st, opts) - st.dirs = {} - st.repos = {} - - opts = opts or {} - opts.order = opts.order or 1500 - - local t = th.git or {} - local styles = { - [CODES.ignored] = t.ignored and ui.Style(t.ignored) or ui.Style():fg("darkgray"), - [CODES.untracked] = t.untracked and ui.Style(t.untracked) or ui.Style():fg("magenta"), - [CODES.modified] = t.modified and ui.Style(t.modified) or ui.Style():fg("yellow"), - [CODES.added] = t.added and ui.Style(t.added) or ui.Style():fg("green"), - [CODES.deleted] = t.deleted and ui.Style(t.deleted) or ui.Style():fg("red"), - [CODES.updated] = t.updated and ui.Style(t.updated) or ui.Style():fg("yellow"), - } - local signs = { - [CODES.ignored] = t.ignored_sign or " ", - [CODES.untracked] = t.untracked_sign or "? ", - [CODES.modified] = t.modified_sign or " ", - [CODES.added] = t.added_sign or " ", - [CODES.deleted] = t.deleted_sign or " ", - [CODES.updated] = t.updated_sign or " ", - } - - Linemode:children_add(function(self) - if not self._file.in_current then - return "" - end - - local url = self._file.url - local repo = st.dirs[tostring(url.base or url.parent)] - local code - if repo then - code = repo == CODES.excluded and CODES.ignored or st.repos[repo][tostring(url):sub(#repo + 2)] - end - - if not code or signs[code] == "" then - return "" - elseif self._file.is_hovered then - return ui.Line { " ", signs[code] } - else - return ui.Line { " ", ui.Span(signs[code]):style(styles[code]) } - end - end, opts.order) + st.dirs = {} + st.repos = {} + + opts = opts or {} + opts.order = opts.order or 1500 + + local t = th.git or {} + local styles = { + [CODES.ignored] = t.ignored and ui.Style(t.ignored) or ui.Style():fg("darkgray"), + [CODES.untracked] = t.untracked and ui.Style(t.untracked) or ui.Style():fg("magenta"), + [CODES.modified] = t.modified and ui.Style(t.modified) or ui.Style():fg("yellow"), + [CODES.added] = t.added and ui.Style(t.added) or ui.Style():fg("green"), + [CODES.deleted] = t.deleted and ui.Style(t.deleted) or ui.Style():fg("red"), + [CODES.updated] = t.updated and ui.Style(t.updated) or ui.Style():fg("yellow"), + } + local signs = { + [CODES.ignored] = t.ignored_sign or " ", + [CODES.untracked] = t.untracked_sign or "? ", + [CODES.modified] = t.modified_sign or " ", + [CODES.added] = t.added_sign or " ", + [CODES.deleted] = t.deleted_sign or " ", + [CODES.updated] = t.updated_sign or " ", + } + + Linemode:children_add(function(self) + if not self._file.in_current then + return "" + end + + local url = self._file.url + local repo = st.dirs[tostring(url.base or url.parent)] + local code + if repo then + code = repo == CODES.excluded and CODES.ignored or st.repos[repo][tostring(url):sub(#repo + 2)] + end + + if not code or signs[code] == "" then + return "" + elseif self._file.is_hovered then + return ui.Line({ " ", signs[code] }) + else + return ui.Line({ " ", ui.Span(signs[code]):style(styles[code]) }) + end + end, opts.order) end ---@type UnstableFetcher local function fetch(_, job) - local cwd = job.files[1].url.base or job.files[1].url.parent - local repo = root(cwd) - if not repo then - remove(tostring(cwd)) - return true - end - - local paths = {} - for _, file in ipairs(job.files) do - paths[#paths + 1] = tostring(file.url) - end + local cwd = job.files[1].url.base or job.files[1].url.parent + local repo = root(cwd) + if not repo then + remove(tostring(cwd)) + return true + end + + local paths = {} + for _, file in ipairs(job.files) do + paths[#paths + 1] = tostring(file.url) + end -- stylua: ignore local output, err = Command("git") @@ -221,35 +221,35 @@ local function fetch(_, job) :arg(paths) :stdout(Command.PIPED) :output() - if not output then - return true, Err("Cannot spawn `git` command, error: %s", err) - end - - local changed, excluded = {}, {} - for line in output.stdout:gmatch("[^\r\n]+") do - local code, path = match(line) - if code == CODES.excluded then - excluded[#excluded + 1] = path - else - changed[path] = code - end - end - - if job.files[1].cha.is_dir then - ya.dict_merge(changed, bubble_up(changed)) - end - ya.dict_merge(changed, propagate_down(excluded, cwd, Url(repo))) - - -- Reset the status of any files that don't appear in the output of `git status` to `unknown`, - -- so that cleaning up outdated statuses from `st.repos` - for _, path in ipairs(paths) do - local s = path:sub(#repo + 2) - changed[s] = changed[s] or CODES.unknown - end - - add(tostring(cwd), repo, changed) - - return false + if not output then + return true, Err("Cannot spawn `git` command, error: %s", err) + end + + local changed, excluded = {}, {} + for line in output.stdout:gmatch("[^\r\n]+") do + local code, path = match(line) + if code == CODES.excluded then + excluded[#excluded + 1] = path + else + changed[path] = code + end + end + + if job.files[1].cha.is_dir then + ya.dict_merge(changed, bubble_up(changed)) + end + ya.dict_merge(changed, propagate_down(excluded, cwd, Url(repo))) + + -- Reset the status of any files that don't appear in the output of `git status` to `unknown`, + -- so that cleaning up outdated statuses from `st.repos` + for _, path in ipairs(paths) do + local s = path:sub(#repo + 2) + changed[s] = changed[s] or CODES.unknown + end + + add(tostring(cwd), repo, changed) + + return false end return { setup = setup, fetch = fetch }