From f5761613b0b047a19b42b5069cf770bde2b0b0d2 Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Wed, 17 Jun 2026 23:15:41 +0900 Subject: [PATCH 1/4] feat: display viewed file count in sidepanel header Co-Authored-By: Claude Opus 4.6 --- lua/fude/files.lua | 15 +++++++++++++++ lua/fude/ui/sidepanel.lua | 15 +++++++++------ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/lua/fude/files.lua b/lua/fude/files.lua index 6c688b5..5dab7cc 100644 --- a/lua/fude/files.lua +++ b/lua/fude/files.lua @@ -45,6 +45,21 @@ function M.comment_count_display(submitted, pending, outdated) return "πŸ’¬" .. total, hl end +--- Count files with VIEWED state among changed files. +--- @param viewed_files table|nil { [path] = "VIEWED" | "UNVIEWED" | "DISMISSED" } +--- @param changed_files table[] list of { path, ... } +--- @return number +function M.count_viewed(viewed_files, changed_files) + viewed_files = viewed_files or {} + local count = 0 + for _, file in ipairs(changed_files) do + if viewed_files[file.path] == "VIEWED" then + count = count + 1 + end + end + return count +end + --- Build normalized file entries from changed files list. --- @param changed_files table[] list of { path, status, additions, deletions, patch } --- @param repo_root string repository root directory diff --git a/lua/fude/ui/sidepanel.lua b/lua/fude/ui/sidepanel.lua index 3e561c6..3a58941 100644 --- a/lua/fude/ui/sidepanel.lua +++ b/lua/fude/ui/sidepanel.lua @@ -56,14 +56,15 @@ end --- @param file_entries table[] entries from files.build_file_entries --- @param width number available width in columns --- @param format_path_fn (fun(s: string): string|nil)|nil formats file path for display (nil = identity) +--- @param viewed_count number number of files with VIEWED state --- @return string[] lines --- @return table[] highlights { { line_0idx, col_start, col_end, hl_group } } --- @return number entry_count number of file entries -function M.format_files_section(file_entries, width, format_path_fn) +function M.format_files_section(file_entries, width, format_path_fn, viewed_count) format_path_fn = format_path_fn or function(p) return p end - local lines = { string.format(" Files (%d)", #file_entries), string.rep("─", width) } + local lines = { string.format(" Files (Reviewed: %d/%d)", viewed_count, #file_entries), string.rep("─", width) } local highlights = { { 0, 0, -1, "Title" }, } @@ -99,11 +100,12 @@ end --- @param tree_entries table[] entries from ui.sidepanel.tree.flatten_tree --- @param total_file_count number total number of changed files --- @param width number available width in columns +--- @param viewed_count number number of files with VIEWED state --- @return string[] lines --- @return table[] highlights { { line_0idx, col_start, col_end, hl_group } } --- @return number entry_count number of rendered tree entries -function M.format_files_section_tree(tree_entries, total_file_count, width) - local lines = { string.format(" Files (%d)", total_file_count), string.rep("─", width) } +function M.format_files_section_tree(tree_entries, total_file_count, width, viewed_count) + local lines = { string.format(" Files (Reviewed: %d/%d)", viewed_count, total_file_count), string.rep("─", width) } local highlights = { { 0, 0, -1, "Title" }, } @@ -284,6 +286,7 @@ local function render(panel) -- Format sections local scope_lines, scope_hls, scope_count = M.format_scope_section(scope_entries, width) + local viewed_count = files_mod.count_viewed(state.viewed_files, state.changed_files or {}) local file_lines, file_hls, file_count local tree_entries if (panel.file_tree_mode or sp_opts.file_tree) == "tree" then @@ -291,9 +294,9 @@ local function render(panel) local tree = tree_mod.build_tree(file_entries) tree_mod.collapse_singleton_chains(tree) tree_entries = tree_mod.flatten_tree(tree, state.viewed_files) - file_lines, file_hls, file_count = M.format_files_section_tree(tree_entries, #file_entries, width) + file_lines, file_hls, file_count = M.format_files_section_tree(tree_entries, #file_entries, width, viewed_count) else - file_lines, file_hls, file_count = M.format_files_section(file_entries, width, config.format_path) + file_lines, file_hls, file_count = M.format_files_section(file_entries, width, config.format_path, viewed_count) end local lines, highlights, section_map = From dfbcb4c692e34f16c5dc85da1a2c6c768b312ddc Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Wed, 17 Jun 2026 23:15:46 +0900 Subject: [PATCH 2/4] test: add tests for viewed count in sidepanel header Co-Authored-By: Claude Opus 4.6 --- tests/fude/files_spec.lua | 36 ++++++++++++++++++++ tests/fude/sidepanel_spec.lua | 62 ++++++++++++++++++++++------------- 2 files changed, 75 insertions(+), 23 deletions(-) diff --git a/tests/fude/files_spec.lua b/tests/fude/files_spec.lua index d0300c1..d761d83 100644 --- a/tests/fude/files_spec.lua +++ b/tests/fude/files_spec.lua @@ -268,6 +268,42 @@ describe("build_file_entries with comment_counts", function() end) end) +describe("count_viewed", function() + it("counts all VIEWED files", function() + local viewed = { ["a.lua"] = "VIEWED", ["b.lua"] = "VIEWED", ["c.lua"] = "VIEWED" } + local changed = { { path = "a.lua" }, { path = "b.lua" }, { path = "c.lua" } } + assert.are.equal(3, files.count_viewed(viewed, changed)) + end) + + it("counts only VIEWED, not UNVIEWED or DISMISSED", function() + local viewed = { ["a.lua"] = "VIEWED", ["b.lua"] = "UNVIEWED", ["c.lua"] = "DISMISSED" } + local changed = { { path = "a.lua" }, { path = "b.lua" }, { path = "c.lua" } } + assert.are.equal(1, files.count_viewed(viewed, changed)) + end) + + it("returns 0 when no files are VIEWED", function() + local viewed = { ["a.lua"] = "UNVIEWED", ["b.lua"] = "UNVIEWED" } + local changed = { { path = "a.lua" }, { path = "b.lua" } } + assert.are.equal(0, files.count_viewed(viewed, changed)) + end) + + it("returns 0 for empty changed_files", function() + local viewed = { ["a.lua"] = "VIEWED" } + assert.are.equal(0, files.count_viewed(viewed, {})) + end) + + it("returns 0 when viewed_files is nil", function() + local changed = { { path = "a.lua" }, { path = "b.lua" } } + assert.are.equal(0, files.count_viewed(nil, changed)) + end) + + it("ignores paths in viewed_files not in changed_files", function() + local viewed = { ["a.lua"] = "VIEWED", ["x.lua"] = "VIEWED" } + local changed = { { path = "a.lua" }, { path = "b.lua" } } + assert.are.equal(1, files.count_viewed(viewed, changed)) + end) +end) + describe("apply_viewed_toggle", function() local config = require("fude.config") local helpers = require("tests.helpers") diff --git a/tests/fude/sidepanel_spec.lua b/tests/fude/sidepanel_spec.lua index 6791f2a..8c36587 100644 --- a/tests/fude/sidepanel_spec.lua +++ b/tests/fude/sidepanel_spec.lua @@ -114,41 +114,41 @@ describe("format_files_section", function() }, } - it("creates header with file count", function() - local lines = sidepanel.format_files_section(file_entries, 40) - assert.truthy(lines[1]:find("Files %(2%)")) + it("creates header with viewed and total file count", function() + local lines = sidepanel.format_files_section(file_entries, 40, nil, 1) + assert.truthy(lines[1]:find("Files %(Reviewed: 1/2%)")) end) it("creates separator as second line", function() - local lines = sidepanel.format_files_section(file_entries, 40) + local lines = sidepanel.format_files_section(file_entries, 40, nil, 1) assert.truthy(lines[2]:find("─")) end) it("creates one line per file entry after header", function() - local lines, _, count = sidepanel.format_files_section(file_entries, 60) + local lines, _, count = sidepanel.format_files_section(file_entries, 60, nil, 1) assert.are.equal(2, count) assert.are.equal(4, #lines) -- header + separator + 2 entries end) it("shows viewed icon", function() - local lines = sidepanel.format_files_section(file_entries, 60) + local lines = sidepanel.format_files_section(file_entries, 60, nil, 1) assert.truthy(lines[3]:find("βœ“")) end) it("shows status icon", function() - local lines = sidepanel.format_files_section(file_entries, 60) + local lines = sidepanel.format_files_section(file_entries, 60, nil, 1) assert.truthy(lines[3]:find("~")) assert.truthy(lines[4]:find("%+")) end) it("shows additions and deletions", function() - local lines = sidepanel.format_files_section(file_entries, 60) + local lines = sidepanel.format_files_section(file_entries, 60, nil, 1) assert.truthy(lines[3]:find("+10")) assert.truthy(lines[3]:find("-5")) end) it("shows file path", function() - local lines = sidepanel.format_files_section(file_entries, 80) + local lines = sidepanel.format_files_section(file_entries, 80, nil, 1) assert.truthy(lines[3]:find("lua/fude/scope.lua")) end) @@ -156,13 +156,13 @@ describe("format_files_section", function() local fn = function(p) return p:match("[^/]+$") end - local lines = sidepanel.format_files_section(file_entries, 80, fn) + local lines = sidepanel.format_files_section(file_entries, 80, fn, 1) assert.truthy(lines[3]:find("scope.lua")) assert.is_falsy(lines[3]:find("lua/fude/scope.lua")) end) it("uses identity when format_path_fn is nil", function() - local lines = sidepanel.format_files_section(file_entries, 80, nil) + local lines = sidepanel.format_files_section(file_entries, 80, nil, 1) assert.truthy(lines[3]:find("lua/fude/scope.lua")) end) @@ -170,21 +170,26 @@ describe("format_files_section", function() local fn = function() return nil end - local lines = sidepanel.format_files_section(file_entries, 80, fn) + local lines = sidepanel.format_files_section(file_entries, 80, fn, 1) assert.truthy(lines[3]:find("lua/fude/scope.lua")) end) it("returns highlights for each file entry", function() - local _, file_hls = sidepanel.format_files_section(file_entries, 60) + local _, file_hls = sidepanel.format_files_section(file_entries, 60, nil, 1) -- header (1) + 4 highlights per file (viewed, status, adds, dels) Γ— 2 files = 9 assert.are.equal(9, #file_hls) end) it("handles empty entries", function() - local lines, _, count = sidepanel.format_files_section({}, 40) + local lines, _, count = sidepanel.format_files_section({}, 40, nil, 0) assert.are.equal(2, #lines) -- header + separator assert.are.equal(0, count) - assert.truthy(lines[1]:find("Files %(0%)")) + assert.truthy(lines[1]:find("Files %(Reviewed: 0/0%)")) + end) + + it("shows all-viewed count in header", function() + local lines = sidepanel.format_files_section(file_entries, 40, nil, 2) + assert.truthy(lines[1]:find("Files %(Reviewed: 2/2%)")) end) end) @@ -208,7 +213,7 @@ describe("format_files_section_tree", function() } end - it("renders header with total file count", function() + it("renders header with viewed and total file count", function() local file_entries = { make_file_entry("a/b.md"), make_file_entry("a/c.md"), @@ -216,15 +221,15 @@ describe("format_files_section_tree", function() } local root = tree.build_tree(file_entries) local entries = tree.flatten_tree(root, {}) - local lines = sidepanel.format_files_section_tree(entries, #file_entries, 40) - assert.are.equal(" Files (3)", lines[1]) + local lines = sidepanel.format_files_section_tree(entries, #file_entries, 40, 0) + assert.are.equal(" Files (Reviewed: 0/3)", lines[1]) end) it("renders directories as indented labels", function() local file_entries = { make_file_entry("a/b/c.md") } local root = tree.build_tree(file_entries) local entries = tree.flatten_tree(root, {}) - local lines = sidepanel.format_files_section_tree(entries, 1, 40) + local lines = sidepanel.format_files_section_tree(entries, 1, 40, 0) assert.are.equal("a", lines[3]) assert.are.equal(" b", lines[4]) assert.is_truthy(lines[5]:find(" ")) @@ -238,7 +243,7 @@ describe("format_files_section_tree", function() } local root = tree.build_tree(file_entries) local entries = tree.flatten_tree(root, {}) - local lines = sidepanel.format_files_section_tree(entries, #file_entries, 40) + local lines = sidepanel.format_files_section_tree(entries, #file_entries, 40, 0) assert.are.equal("a", lines[3]) end) @@ -251,7 +256,7 @@ describe("format_files_section_tree", function() } local root = tree.build_tree(file_entries) local entries = tree.flatten_tree(root, { ["a/b.md"] = "VIEWED", ["a/c.md"] = "VIEWED" }) - local lines = sidepanel.format_files_section_tree(entries, #file_entries, 40) + local lines = sidepanel.format_files_section_tree(entries, #file_entries, 40, 2) assert.are.equal("a ●", lines[3]) end) @@ -260,7 +265,7 @@ describe("format_files_section_tree", function() local file_entries = { make_file_entry("a/foo.md", { additions = 7, deletions = 2 }) } local root = tree.build_tree(file_entries) local entries = tree.flatten_tree(root, {}) - local lines = sidepanel.format_files_section_tree(entries, 1, 40) + local lines = sidepanel.format_files_section_tree(entries, 1, 40, 0) assert.truthy(lines[4]:find("~")) assert.truthy(lines[4]:find("%+7")) assert.truthy(lines[4]:find("%-2")) @@ -274,9 +279,20 @@ describe("format_files_section_tree", function() } local root = tree.build_tree(file_entries) local entries = tree.flatten_tree(root, {}) - local _, _, count = sidepanel.format_files_section_tree(entries, #file_entries, 40) + local _, _, count = sidepanel.format_files_section_tree(entries, #file_entries, 40, 0) assert.are.equal(3, count) end) + + it("shows viewed count in header", function() + local file_entries = { + make_file_entry("a/b.md"), + make_file_entry("a/c.md"), + } + local root = tree.build_tree(file_entries) + local entries = tree.flatten_tree(root, {}) + local lines = sidepanel.format_files_section_tree(entries, #file_entries, 40, 1) + assert.are.equal(" Files (Reviewed: 1/2)", lines[1]) + end) end) describe("build_sidepanel_content", function() From c978b195f904ed6ce6b78cdc14d057f3ab25044e Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Wed, 17 Jun 2026 23:15:49 +0900 Subject: [PATCH 3/4] docs: update sidepanel viewed count documentation Co-Authored-By: Claude Opus 4.6 --- CLAUDE.md | 4 ++-- doc/fude.txt | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 6f0ecdb..f1a33f8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -42,10 +42,10 @@ All plugin code lives under `lua/fude/`. The plugin entry point is `plugin/fude. - **`ui/format.lua`** β€” Pure format/calculation functions with no state or vim API side effects: `calculate_float_dimensions`, `format_comments_for_display`, `normalize_check`, `format_check_status`, `deduplicate_checks`, `sort_checks`, `build_checks_summary`, `format_review_status`, `build_reviewers_list`, `build_reviewers_summary`, `calculate_overview_layout`, `calculate_comments_height`, `calculate_reply_window_dimensions`, `format_reply_comments_for_display`, `build_overview_left_lines`, `build_overview_right_lines`, `calculate_comment_browser_layout`, `format_comment_browser_list`, `format_comment_browser_thread`, `parse_markdown_line`, `build_highlighted_chunks`, `apply_markdown_highlight_to_line`, `normalize_newlines`. Purity is enforced by `scripts/check_purity.lua` (`make check-purity`). - **`ui/inline.lua`** β€” Inline (virt_lines) comment box rendering. `format_comments_for_inline(comments, format_date_fn, opts)` builds bordered comment boxes for use as `nvim_buf_set_extmark` `virt_lines`. Reads the current window's text width via `vim.api`/`vim.fn` to size boxes, which is why it lives outside the pure `ui/format.lua`. - **`ui/comment_browser.lua`** β€” 3-pane floating comment browser for `FudeReviewListComments`. Left pane: comment list (review + PR-level, time-descending), including local drafts via `merge_draft_entries` (existing entries get a `✎draft` marker; new line/suggest/issue drafts appear as `[draft]` rows). Selecting a `type="draft"` row: `line`/`suggest` jumps to the file (``), `issue` focuses the lower pane (prefilled). Right upper: thread display (draft body preview for draft rows). Right lower: reply/edit/new comment input. Supports reply, edit, delete, new PR comment, jump to file, and refresh. Does not depend on Telescope. - - **`ui/sidepanel.lua`** β€” Toggleable sidebar showing Review Scope and Changed Files. Files default to a flat list (`sidepanel.file_tree = "flat"`); `"tree"` groups files by directory via `ui/sidepanel/tree.lua`. Pure functions: `format_scope_section`, `format_files_section`, `format_files_section_tree`, `build_sidepanel_content`, `resolve_entry_at_cursor`. Side-effect functions: `open`, `close`, `toggle`, `refresh`, `toggle_file_tree_mode`. Uses `nvim_open_win` with `split` for sidebar creation. Uses dedicated `fude_sidepanel` namespace for highlights (avoids `refresh_extmarks` clearing them on BufEnter). Auto-refreshes on scope change and reload. Keymaps: `` select/open, `` toggle reviewed/viewed, `t` toggle flat/tree, `R` reload, `q` close. + - **`ui/sidepanel.lua`** β€” Toggleable sidebar showing Review Scope and Changed Files. Files default to a flat list (`sidepanel.file_tree = "flat"`); `"tree"` groups files by directory via `ui/sidepanel/tree.lua`. Pure functions: `format_scope_section`, `format_files_section(file_entries, width, format_path_fn, viewed_count)`, `format_files_section_tree(tree_entries, total_file_count, width, viewed_count)`, `build_sidepanel_content`, `resolve_entry_at_cursor`. The Files section header displays viewed/total count (e.g., "Files (Reviewed: 3/10)"). Side-effect functions: `open`, `close`, `toggle`, `refresh`, `toggle_file_tree_mode`. Uses `nvim_open_win` with `split` for sidebar creation. Uses dedicated `fude_sidepanel` namespace for highlights (avoids `refresh_extmarks` clearing them on BufEnter). Auto-refreshes on scope change and reload. Keymaps: `` select/open, `` toggle reviewed/viewed, `t` toggle flat/tree, `R` reload, `q` close. - **`ui/sidepanel/tree.lua`** β€” Pure tree helpers for the sidepanel Files section: `build_tree`, `collapse_singleton_chains`, `compute_aggregate`, and `flatten_tree`. - **`ui/extmarks.lua`** β€” Extmark management: `flash_line`, `highlight_comment_lines`, `clear_comment_line_highlight`, `refresh_extmarks`, `clear_extmarks`, `clear_all_extmarks`. Uses lazy `require("fude.comments")` to avoid circular dependencies. `refresh_extmarks` also renders the `draft` indicator from `drafts.file_markers(rel_path)` (EOL virt_text, both virtualText and inline modes). -- **`files.lua`** β€” Changed files display via Telescope picker, snacks.picker, or quickfix list. All pickers show diff preview and viewed state toggle via ``. Shows GitHub viewed status for each file. Exports `apply_viewed_toggle(path, on_done)` as a picker-agnostic state mutator that invokes gh GraphQL mark/unmark, updates `state.viewed_files`, and calls `on_done` with the updated display fields; the Telescope adapter `toggle_viewed_in_telescope` and the snacks adapter `toggle_viewed_in_snacks(picker, item)` both delegate to it. `show()` routes to `show_telescope` / `show_snacks` / `show_quickfix` based on `config.opts.file_list_mode`; snacks falls back to quickfix when snacks.nvim is missing. Also exports `next_file()` / `prev_file()` for jumping between changed files (used by `:FudeReviewNextFile` / `:FudeReviewPrevFile`); both wrap around at the edges and fall back to the first/last entry when the current buffer is not part of the PR. The pure helper `find_adjacent_file_index(changed_files, current_path, direction)` computes the target index. +- **`files.lua`** β€” Changed files display via Telescope picker, snacks.picker, or quickfix list. All pickers show diff preview and viewed state toggle via ``. Shows GitHub viewed status for each file. Exports `apply_viewed_toggle(path, on_done)` as a picker-agnostic state mutator that invokes gh GraphQL mark/unmark, updates `state.viewed_files`, and calls `on_done` with the updated display fields; the Telescope adapter `toggle_viewed_in_telescope` and the snacks adapter `toggle_viewed_in_snacks(picker, item)` both delegate to it. `show()` routes to `show_telescope` / `show_snacks` / `show_quickfix` based on `config.opts.file_list_mode`; snacks falls back to quickfix when snacks.nvim is missing. Also exports `next_file()` / `prev_file()` for jumping between changed files (used by `:FudeReviewNextFile` / `:FudeReviewPrevFile`); both wrap around at the edges and fall back to the first/last entry when the current buffer is not part of the PR. The pure helpers `find_adjacent_file_index(changed_files, current_path, direction)` computes the target index; `count_viewed(viewed_files, changed_files)` counts files with VIEWED state (used by sidepanel header). - **`scope.lua`** β€” Review scope selection and navigation. Provides a Telescope picker (or `vim.ui.select` fallback) for choosing between full PR scope and individual commit scope, with commit index display (`[1/10]`) and current scope marker (`β–Ά`). Supports next/prev scope navigation (`next_scope`/`prev_scope`), marking commits as reviewed via `` in the Telescope picker (tracked locally in `state.reviewed_commits`), and statusline integration (`statusline()`). On commit scope: checks out the commit, fetches commit-specific changed files, updates gitsigns base to `sha^` (global), and refreshes the diff preview. On full PR scope: restores the original HEAD, re-fetches PR-wide changed files, and computes merge-base (per-buffer gitsigns base is applied via `GitSignsUpdate` autocmd in init.lua). Exports `apply_reviewed_toggle(sha)` as a picker-agnostic state mutator that toggles `state.reviewed_commits[sha]` and returns the updated display fields `{ is_reviewed, reviewed_icon, reviewed_hl }`; both the Telescope adapter `toggle_reviewed_in_telescope` and the snacks adapter `toggle_reviewed_in_snacks(picker, item)` delegate to it. `select_scope()` routes to `show_telescope` / `show_snacks` / `show_vim_select` based on `config.opts.file_list_mode`; snacks falls back to `vim.ui.select` when snacks.nvim is missing. - **`overview.lua`** β€” PR overview display: fetches extended PR info and issue-level comments, renders in a centered float with keymaps for commenting and refreshing. - **`completion/init.lua`** β€” Completion source for comment input buffers. Provides mention (`@user`), issue/PR (`#nnn`), and commit SHA completion candidates via `fetch_mentions`, `fetch_issues`, `fetch_commits`, with a 5-minute TTL cache. `build_commit_items(commit_entries)` is a pure helper that formats commit entries into completion items; `get_context(line_before_cursor)` parses the trigger character (`@`/`#`/sha prefix). Reads `state.pr_commits` (only) for commit completion freshness invalidation. diff --git a/doc/fude.txt b/doc/fude.txt index 4969a83..a426fdf 100644 --- a/doc/fude.txt +++ b/doc/fude.txt @@ -347,7 +347,8 @@ Using lazy.nvim: >lua Files:~ Lists all changed files in the current scope with status icons, - additions/deletions counts, and viewed state. + additions/deletions counts, and viewed state. The header shows + the viewed file count out of total (e.g., "Files (Reviewed: 3/10)"). Keymaps (panel):~ `` Select scope entry or open file From b7b2a74d9931912799505e00c148bb3f0ed4cc33 Mon Sep 17 00:00:00 2001 From: kyu08 <49891479+kyu08@users.noreply.github.com> Date: Wed, 17 Jun 2026 23:34:57 +0900 Subject: [PATCH 4/4] fix: add nil guard and fix data source alignment for viewed_count Co-Authored-By: Claude Opus 4.6 --- lua/fude/ui/sidepanel.lua | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lua/fude/ui/sidepanel.lua b/lua/fude/ui/sidepanel.lua index 3a58941..d22f202 100644 --- a/lua/fude/ui/sidepanel.lua +++ b/lua/fude/ui/sidepanel.lua @@ -56,7 +56,7 @@ end --- @param file_entries table[] entries from files.build_file_entries --- @param width number available width in columns --- @param format_path_fn (fun(s: string): string|nil)|nil formats file path for display (nil = identity) ---- @param viewed_count number number of files with VIEWED state +--- @param viewed_count number count of files with VIEWED state --- @return string[] lines --- @return table[] highlights { { line_0idx, col_start, col_end, hl_group } } --- @return number entry_count number of file entries @@ -64,6 +64,7 @@ function M.format_files_section(file_entries, width, format_path_fn, viewed_coun format_path_fn = format_path_fn or function(p) return p end + viewed_count = viewed_count or 0 local lines = { string.format(" Files (Reviewed: %d/%d)", viewed_count, #file_entries), string.rep("─", width) } local highlights = { { 0, 0, -1, "Title" }, @@ -100,11 +101,12 @@ end --- @param tree_entries table[] entries from ui.sidepanel.tree.flatten_tree --- @param total_file_count number total number of changed files --- @param width number available width in columns ---- @param viewed_count number number of files with VIEWED state +--- @param viewed_count number count of files with VIEWED state --- @return string[] lines --- @return table[] highlights { { line_0idx, col_start, col_end, hl_group } } --- @return number entry_count number of rendered tree entries function M.format_files_section_tree(tree_entries, total_file_count, width, viewed_count) + viewed_count = viewed_count or 0 local lines = { string.format(" Files (Reviewed: %d/%d)", viewed_count, total_file_count), string.rep("─", width) } local highlights = { { 0, 0, -1, "Title" }, @@ -271,6 +273,7 @@ local function render(panel) -- Build file entries (skip if repo root unavailable) local repo_root = diff_mod.get_repo_root() local file_entries = {} + local viewed_count = 0 if repo_root then local viewed_sign = (config.opts.signs and config.opts.signs.viewed) or "βœ“" local comment_counts = comments_data.build_file_comment_counts(state.comments, state.pending_comments) @@ -282,11 +285,11 @@ local function render(panel) viewed_sign, comment_counts ) + viewed_count = files_mod.count_viewed(state.viewed_files, state.changed_files or {}) end -- Format sections local scope_lines, scope_hls, scope_count = M.format_scope_section(scope_entries, width) - local viewed_count = files_mod.count_viewed(state.viewed_files, state.changed_files or {}) local file_lines, file_hls, file_count local tree_entries if (panel.file_tree_mode or sp_opts.file_tree) == "tree" then