diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..0ea2157f --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,9 @@ +--- +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + labels: + - "CI/CD" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 3008efe3..ed481a13 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 - - uses: JohnnyMorganz/stylua-action@v1 + - uses: JohnnyMorganz/stylua-action@v4 with: token: ${{ secrets.GITHUB_TOKEN }} args: --check lua/ diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0851d776..25f77717 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -15,10 +15,10 @@ jobs: manager: sudo apt-get packages: -y fd-find steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - run: date +%F > todays-date - name: Restore from todays cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: _neovim key: ${{ runner.os }}-${{ matrix.url }}-${{ hashFiles('todays-date') }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 098de904..b64ec12c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,3 +1,7 @@ +--- +ci: + autofix_prs: false + repos: - repo: https://github.com/JohnnyMorganz/StyLua rev: v0.20.0 diff --git a/README.md b/README.md index a8df9c44..c32c3acd 100644 --- a/README.md +++ b/README.md @@ -168,74 +168,74 @@ require"octo".setup({ mappings_disable_default = false, -- disable default mappings if true, but will still adapt user mappings mappings = { issue = { - close_issue = { lhs = "ic", desc = "close issue" }, - reopen_issue = { lhs = "io", desc = "reopen issue" }, - list_issues = { lhs = "il", desc = "list open issues on same repo" }, + close_issue = { lhs = "ic", desc = "close issue" }, + reopen_issue = { lhs = "io", desc = "reopen issue" }, + list_issues = { lhs = "il", desc = "list open issues on same repo" }, reload = { lhs = "", desc = "reload issue" }, open_in_browser = { lhs = "", desc = "open issue in browser" }, copy_url = { lhs = "", desc = "copy url to system clipboard" }, - add_assignee = { lhs = "aa", desc = "add assignee" }, - remove_assignee = { lhs = "ad", desc = "remove assignee" }, - create_label = { lhs = "lc", desc = "create label" }, - add_label = { lhs = "la", desc = "add label" }, - remove_label = { lhs = "ld", desc = "remove label" }, - goto_issue = { lhs = "gi", desc = "navigate to a local repo issue" }, - add_comment = { lhs = "ca", desc = "add comment" }, - delete_comment = { lhs = "cd", desc = "delete comment" }, + add_assignee = { lhs = "aa", desc = "add assignee" }, + remove_assignee = { lhs = "ad", desc = "remove assignee" }, + create_label = { lhs = "lc", desc = "create label" }, + add_label = { lhs = "la", desc = "add label" }, + remove_label = { lhs = "ld", desc = "remove label" }, + goto_issue = { lhs = "gi", desc = "navigate to a local repo issue" }, + add_comment = { lhs = "ca", desc = "add comment" }, + delete_comment = { lhs = "cd", desc = "delete comment" }, next_comment = { lhs = "]c", desc = "go to next comment" }, prev_comment = { lhs = "[c", desc = "go to previous comment" }, - react_hooray = { lhs = "rp", desc = "add/remove 🎉 reaction" }, - react_heart = { lhs = "rh", desc = "add/remove ❤️ reaction" }, - react_eyes = { lhs = "re", desc = "add/remove 👀 reaction" }, - react_thumbs_up = { lhs = "r+", desc = "add/remove 👍 reaction" }, - react_thumbs_down = { lhs = "r-", desc = "add/remove 👎 reaction" }, - react_rocket = { lhs = "rr", desc = "add/remove 🚀 reaction" }, - react_laugh = { lhs = "rl", desc = "add/remove 😄 reaction" }, - react_confused = { lhs = "rc", desc = "add/remove 😕 reaction" }, + react_hooray = { lhs = "rp", desc = "add/remove 🎉 reaction" }, + react_heart = { lhs = "rh", desc = "add/remove ❤️ reaction" }, + react_eyes = { lhs = "re", desc = "add/remove 👀 reaction" }, + react_thumbs_up = { lhs = "r+", desc = "add/remove 👍 reaction" }, + react_thumbs_down = { lhs = "r-", desc = "add/remove 👎 reaction" }, + react_rocket = { lhs = "rr", desc = "add/remove 🚀 reaction" }, + react_laugh = { lhs = "rl", desc = "add/remove 😄 reaction" }, + react_confused = { lhs = "rc", desc = "add/remove 😕 reaction" }, }, pull_request = { - checkout_pr = { lhs = "po", desc = "checkout PR" }, - merge_pr = { lhs = "pm", desc = "merge commit PR" }, - squash_and_merge_pr = { lhs = "psm", desc = "squash and merge PR" }, - rebase_and_merge_pr = { lhs = "prm", desc = "rebase and merge PR" }, - list_commits = { lhs = "pc", desc = "list PR commits" }, - list_changed_files = { lhs = "pf", desc = "list PR changed files" }, - show_pr_diff = { lhs = "pd", desc = "show PR diff" }, - add_reviewer = { lhs = "va", desc = "add reviewer" }, - remove_reviewer = { lhs = "vd", desc = "remove reviewer request" }, - close_issue = { lhs = "ic", desc = "close PR" }, - reopen_issue = { lhs = "io", desc = "reopen PR" }, - list_issues = { lhs = "il", desc = "list open issues on same repo" }, + checkout_pr = { lhs = "po", desc = "checkout PR" }, + merge_pr = { lhs = "pm", desc = "merge commit PR" }, + squash_and_merge_pr = { lhs = "psm", desc = "squash and merge PR" }, + rebase_and_merge_pr = { lhs = "prm", desc = "rebase and merge PR" }, + list_commits = { lhs = "pc", desc = "list PR commits" }, + list_changed_files = { lhs = "pf", desc = "list PR changed files" }, + show_pr_diff = { lhs = "pd", desc = "show PR diff" }, + add_reviewer = { lhs = "va", desc = "add reviewer" }, + remove_reviewer = { lhs = "vd", desc = "remove reviewer request" }, + close_issue = { lhs = "ic", desc = "close PR" }, + reopen_issue = { lhs = "io", desc = "reopen PR" }, + list_issues = { lhs = "il", desc = "list open issues on same repo" }, reload = { lhs = "", desc = "reload PR" }, open_in_browser = { lhs = "", desc = "open PR in browser" }, copy_url = { lhs = "", desc = "copy url to system clipboard" }, goto_file = { lhs = "gf", desc = "go to file" }, - add_assignee = { lhs = "aa", desc = "add assignee" }, - remove_assignee = { lhs = "ad", desc = "remove assignee" }, - create_label = { lhs = "lc", desc = "create label" }, - add_label = { lhs = "la", desc = "add label" }, - remove_label = { lhs = "ld", desc = "remove label" }, - goto_issue = { lhs = "gi", desc = "navigate to a local repo issue" }, - add_comment = { lhs = "ca", desc = "add comment" }, - delete_comment = { lhs = "cd", desc = "delete comment" }, + add_assignee = { lhs = "aa", desc = "add assignee" }, + remove_assignee = { lhs = "ad", desc = "remove assignee" }, + create_label = { lhs = "lc", desc = "create label" }, + add_label = { lhs = "la", desc = "add label" }, + remove_label = { lhs = "ld", desc = "remove label" }, + goto_issue = { lhs = "gi", desc = "navigate to a local repo issue" }, + add_comment = { lhs = "ca", desc = "add comment" }, + delete_comment = { lhs = "cd", desc = "delete comment" }, next_comment = { lhs = "]c", desc = "go to next comment" }, prev_comment = { lhs = "[c", desc = "go to previous comment" }, - react_hooray = { lhs = "rp", desc = "add/remove 🎉 reaction" }, - react_heart = { lhs = "rh", desc = "add/remove ❤️ reaction" }, - react_eyes = { lhs = "re", desc = "add/remove 👀 reaction" }, - react_thumbs_up = { lhs = "r+", desc = "add/remove 👍 reaction" }, - react_thumbs_down = { lhs = "r-", desc = "add/remove 👎 reaction" }, - react_rocket = { lhs = "rr", desc = "add/remove 🚀 reaction" }, - react_laugh = { lhs = "rl", desc = "add/remove 😄 reaction" }, - react_confused = { lhs = "rc", desc = "add/remove 😕 reaction" }, - review_start = { lhs = "vs", desc = "start a review for the current PR" }, - review_resume = { lhs = "vr", desc = "resume a pending review for the current PR" }, + react_hooray = { lhs = "rp", desc = "add/remove 🎉 reaction" }, + react_heart = { lhs = "rh", desc = "add/remove ❤️ reaction" }, + react_eyes = { lhs = "re", desc = "add/remove 👀 reaction" }, + react_thumbs_up = { lhs = "r+", desc = "add/remove 👍 reaction" }, + react_thumbs_down = { lhs = "r-", desc = "add/remove 👎 reaction" }, + react_rocket = { lhs = "rr", desc = "add/remove 🚀 reaction" }, + react_laugh = { lhs = "rl", desc = "add/remove 😄 reaction" }, + react_confused = { lhs = "rc", desc = "add/remove 😕 reaction" }, + review_start = { lhs = "vs", desc = "start a review for the current PR" }, + review_resume = { lhs = "vr", desc = "resume a pending review for the current PR" }, }, review_thread = { - goto_issue = { lhs = "gi", desc = "navigate to a local repo issue" }, - add_comment = { lhs = "ca", desc = "add comment" }, - add_suggestion = { lhs = "sa", desc = "add suggestion" }, - delete_comment = { lhs = "cd", desc = "delete comment" }, + goto_issue = { lhs = "gi", desc = "navigate to a local repo issue" }, + add_comment = { lhs = "ca", desc = "add comment" }, + add_suggestion = { lhs = "sa", desc = "add suggestion" }, + delete_comment = { lhs = "cd", desc = "delete comment" }, next_comment = { lhs = "]c", desc = "go to next comment" }, prev_comment = { lhs = "[c", desc = "go to previous comment" }, select_next_entry = { lhs = "]q", desc = "move to next changed file" }, @@ -243,14 +243,14 @@ require"octo".setup({ select_first_entry = { lhs = "[Q", desc = "move to first changed file" }, select_last_entry = { lhs = "]Q", desc = "move to last changed file" }, close_review_tab = { lhs = "", desc = "close review tab" }, - react_hooray = { lhs = "rp", desc = "add/remove 🎉 reaction" }, - react_heart = { lhs = "rh", desc = "add/remove ❤️ reaction" }, - react_eyes = { lhs = "re", desc = "add/remove 👀 reaction" }, - react_thumbs_up = { lhs = "r+", desc = "add/remove 👍 reaction" }, - react_thumbs_down = { lhs = "r-", desc = "add/remove 👎 reaction" }, - react_rocket = { lhs = "rr", desc = "add/remove 🚀 reaction" }, - react_laugh = { lhs = "rl", desc = "add/remove 😄 reaction" }, - react_confused = { lhs = "rc", desc = "add/remove 😕 reaction" }, + react_hooray = { lhs = "rp", desc = "add/remove 🎉 reaction" }, + react_heart = { lhs = "rh", desc = "add/remove ❤️ reaction" }, + react_eyes = { lhs = "re", desc = "add/remove 👀 reaction" }, + react_thumbs_up = { lhs = "r+", desc = "add/remove 👍 reaction" }, + react_thumbs_down = { lhs = "r-", desc = "add/remove 👎 reaction" }, + react_rocket = { lhs = "rr", desc = "add/remove 🚀 reaction" }, + react_laugh = { lhs = "rl", desc = "add/remove 😄 reaction" }, + react_confused = { lhs = "rc", desc = "add/remove 😕 reaction" }, }, submit_win = { approve_review = { lhs = "", desc = "approve review" }, @@ -259,12 +259,12 @@ require"octo".setup({ close_review_tab = { lhs = "", desc = "close review tab" }, }, review_diff = { - submit_review = { lhs = "vs", desc = "submit review" }, - discard_review = { lhs = "vd", desc = "discard review" }, - add_review_comment = { lhs = "ca", desc = "add a new review comment" }, - add_review_suggestion = { lhs = "sa", desc = "add a new review suggestion" }, - focus_files = { lhs = "e", desc = "move focus to changed file panel" }, - toggle_files = { lhs = "b", desc = "hide/show changed files panel" }, + submit_review = { lhs = "vs", desc = "submit review" }, + discard_review = { lhs = "vd", desc = "discard review" }, + add_review_comment = { lhs = "ca", desc = "add a new review comment" }, + add_review_suggestion = { lhs = "sa", desc = "add a new review suggestion" }, + focus_files = { lhs = "e", desc = "move focus to changed file panel" }, + toggle_files = { lhs = "b", desc = "hide/show changed files panel" }, next_thread = { lhs = "]t", desc = "move to next thread" }, prev_thread = { lhs = "[t", desc = "move to previous thread" }, select_next_entry = { lhs = "]q", desc = "move to next changed file" }, @@ -272,24 +272,24 @@ require"octo".setup({ select_first_entry = { lhs = "[Q", desc = "move to first changed file" }, select_last_entry = { lhs = "]Q", desc = "move to last changed file" }, close_review_tab = { lhs = "", desc = "close review tab" }, - toggle_viewed = { lhs = "", desc = "toggle viewer viewed state" }, + toggle_viewed = { lhs = "", desc = "toggle viewer viewed state" }, goto_file = { lhs = "gf", desc = "go to file" }, }, file_panel = { - submit_review = { lhs = "vs", desc = "submit review" }, - discard_review = { lhs = "vd", desc = "discard review" }, + submit_review = { lhs = "vs", desc = "submit review" }, + discard_review = { lhs = "vd", desc = "discard review" }, next_entry = { lhs = "j", desc = "move to next changed file" }, prev_entry = { lhs = "k", desc = "move to previous changed file" }, select_entry = { lhs = "", desc = "show selected changed file diffs" }, refresh_files = { lhs = "R", desc = "refresh changed files panel" }, - focus_files = { lhs = "e", desc = "move focus to changed file panel" }, - toggle_files = { lhs = "b", desc = "hide/show changed files panel" }, + focus_files = { lhs = "e", desc = "move focus to changed file panel" }, + toggle_files = { lhs = "b", desc = "hide/show changed files panel" }, select_next_entry = { lhs = "]q", desc = "move to next changed file" }, select_prev_entry = { lhs = "[q", desc = "move to previous changed file" }, select_first_entry = { lhs = "[Q", desc = "move to first changed file" }, select_last_entry = { lhs = "]Q", desc = "move to last changed file" }, close_review_tab = { lhs = "", desc = "close review tab" }, - toggle_viewed = { lhs = "", desc = "toggle viewer viewed state" }, + toggle_viewed = { lhs = "", desc = "toggle viewer viewed state" }, }, }, }) @@ -443,7 +443,7 @@ Octo search assignee:pwntester is:pr - Enter review mode for the current branch with `Octo review`. Alternatively open the PR (e.g. `Octo ` or `Octo pr list` or `Octo pr edit `) then use `Octo review` in the PR buffer to enter review mode for a specific PR. - A new tab will show a panel with changed files and two windows showing the diff on any of them. - Change panel entries with `]q` and `[q` or by selecting an entry in the window -- Add comments with `ca` or suggestions with `sa` on single or multiple visual-selected lines +- Add comments with `ca` or suggestions with `sa` on single or multiple visual-selected lines - A new buffer will appear in the alternate diff window. The cursor will be positioned in the new buffer - When ready, save the buffer to commit changes to GitHub - Move back to the diff window and move the cursor, the thread buffer will hide diff --git a/doc/octo.txt b/doc/octo.txt index a05b1edf..76c60c91 100644 --- a/doc/octo.txt +++ b/doc/octo.txt @@ -196,7 +196,7 @@ PR REVIEW *octo-pr-review* * Start a review with `Octo review start` or resume a pending review with `Octo review resume` A new tab will show a panel with changed files and two windows showing the diff on any of them. Change panel entries with `]q` and `[q` or by selecting an entry in the window. -* Add comments with `ca` or suggestions with `sa` on single or +* Add comments with `ca` or suggestions with `sa` on single or multiple visual-selected lines * A new buffer will appear in the alternate diff window. Cursor will be positioned in the new buffer diff --git a/lua/octo/commands.lua b/lua/octo/commands.lua index 670191f6..3d0b1e92 100644 --- a/lua/octo/commands.lua +++ b/lua/octo/commands.lua @@ -607,8 +607,10 @@ function M.delete_comment() local split = string.match(bufname, "octo://.+/review/[^/]+/threads/([^/]+)/.*") if split then local layout = reviews.get_current_review().layout - local file = layout:cur_file() - local diff_win = file:get_win(split) + local file = layout:get_current_file() + if not file then + return + end local thread_win = file:get_alternative_win(split) local original_buf = file:get_alternative_buf(split) -- move focus to the split containing the diff buffer @@ -1437,7 +1439,7 @@ function M.reload(opts) require("octo").load_buffer(opts) end -function random_hex_color() +function M.random_hex_color() local chars = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F" } math.randomseed(os.time()) local color = {} @@ -1459,7 +1461,7 @@ function M.create_label(label) local name, color, description if label then name = label - color = random_hex_color() + color = M.random_hex_color() description = "" else vim.fn.inputsave() @@ -1468,7 +1470,7 @@ function M.create_label(label) description = vim.fn.input "Enter description: " vim.fn.inputrestore() if color == "" then - color = random_hex_color() + color = M.random_hex_color() end color = string.gsub(color, "#", "") end diff --git a/lua/octo/config.lua b/lua/octo/config.lua index e05532dd..dc2569fb 100644 --- a/lua/octo/config.lua +++ b/lua/octo/config.lua @@ -4,7 +4,7 @@ local M = {} ---@alias OctoMappingsWindow "issue" | "pull_request" | "review_thread" | "submit_win" | "review_diff" | "file_panel" | "repo" ---@alias OctoMappingsList { [string]: table} ---@alias OctoPickers "telescope" | "fzf-lua" ----@alias OctoFocus "right" | "left" +---@alias OctoSplit "right" | "left" ---@class OctoPickerConfig ---@field use_emojis boolean @@ -38,7 +38,7 @@ local M = {} ---@class OctoConfigReviews ---@field auto_show_threads boolean ----@field focus OctoFocus +---@field focus OctoSplit ---@class OctoConfigPR ---@field order_by OctoConfigOrderBy @@ -168,74 +168,74 @@ function M.get_default_values() mappings_disable_default = false, mappings = { issue = { - close_issue = { lhs = "ic", desc = "close issue" }, - reopen_issue = { lhs = "io", desc = "reopen issue" }, - list_issues = { lhs = "il", desc = "list open issues on same repo" }, + close_issue = { lhs = "ic", desc = "close issue" }, + reopen_issue = { lhs = "io", desc = "reopen issue" }, + list_issues = { lhs = "il", desc = "list open issues on same repo" }, reload = { lhs = "", desc = "reload issue" }, open_in_browser = { lhs = "", desc = "open issue in browser" }, copy_url = { lhs = "", desc = "copy url to system clipboard" }, - add_assignee = { lhs = "aa", desc = "add assignee" }, - remove_assignee = { lhs = "ad", desc = "remove assignee" }, - create_label = { lhs = "lc", desc = "create label" }, - add_label = { lhs = "la", desc = "add label" }, - remove_label = { lhs = "ld", desc = "remove label" }, - goto_issue = { lhs = "gi", desc = "navigate to a local repo issue" }, - add_comment = { lhs = "ca", desc = "add comment" }, - delete_comment = { lhs = "cd", desc = "delete comment" }, + add_assignee = { lhs = "aa", desc = "add assignee" }, + remove_assignee = { lhs = "ad", desc = "remove assignee" }, + create_label = { lhs = "lc", desc = "create label" }, + add_label = { lhs = "la", desc = "add label" }, + remove_label = { lhs = "ld", desc = "remove label" }, + goto_issue = { lhs = "gi", desc = "navigate to a local repo issue" }, + add_comment = { lhs = "ca", desc = "add comment" }, + delete_comment = { lhs = "cd", desc = "delete comment" }, next_comment = { lhs = "]c", desc = "go to next comment" }, prev_comment = { lhs = "[c", desc = "go to previous comment" }, - react_hooray = { lhs = "rp", desc = "add/remove 🎉 reaction" }, - react_heart = { lhs = "rh", desc = "add/remove ❤️ reaction" }, - react_eyes = { lhs = "re", desc = "add/remove 👀 reaction" }, - react_thumbs_up = { lhs = "r+", desc = "add/remove 👍 reaction" }, - react_thumbs_down = { lhs = "r-", desc = "add/remove 👎 reaction" }, - react_rocket = { lhs = "rr", desc = "add/remove 🚀 reaction" }, - react_laugh = { lhs = "rl", desc = "add/remove 😄 reaction" }, - react_confused = { lhs = "rc", desc = "add/remove 😕 reaction" }, + react_hooray = { lhs = "rp", desc = "add/remove 🎉 reaction" }, + react_heart = { lhs = "rh", desc = "add/remove ❤️ reaction" }, + react_eyes = { lhs = "re", desc = "add/remove 👀 reaction" }, + react_thumbs_up = { lhs = "r+", desc = "add/remove 👍 reaction" }, + react_thumbs_down = { lhs = "r-", desc = "add/remove 👎 reaction" }, + react_rocket = { lhs = "rr", desc = "add/remove 🚀 reaction" }, + react_laugh = { lhs = "rl", desc = "add/remove 😄 reaction" }, + react_confused = { lhs = "rc", desc = "add/remove 😕 reaction" }, }, pull_request = { - checkout_pr = { lhs = "po", desc = "checkout PR" }, - merge_pr = { lhs = "pm", desc = "merge commit PR" }, - squash_and_merge_pr = { lhs = "psm", desc = "squash and merge PR" }, - rebase_and_merge_pr = { lhs = "prm", desc = "rebase and merge PR" }, - list_commits = { lhs = "pc", desc = "list PR commits" }, - list_changed_files = { lhs = "pf", desc = "list PR changed files" }, - show_pr_diff = { lhs = "pd", desc = "show PR diff" }, - add_reviewer = { lhs = "va", desc = "add reviewer" }, - remove_reviewer = { lhs = "vd", desc = "remove reviewer request" }, - close_issue = { lhs = "ic", desc = "close PR" }, - reopen_issue = { lhs = "io", desc = "reopen PR" }, - list_issues = { lhs = "il", desc = "list open issues on same repo" }, + checkout_pr = { lhs = "po", desc = "checkout PR" }, + merge_pr = { lhs = "pm", desc = "merge commit PR" }, + squash_and_merge_pr = { lhs = "psm", desc = "squash and merge PR" }, + rebase_and_merge_pr = { lhs = "prm", desc = "rebase and merge PR" }, + list_commits = { lhs = "pc", desc = "list PR commits" }, + list_changed_files = { lhs = "pf", desc = "list PR changed files" }, + show_pr_diff = { lhs = "pd", desc = "show PR diff" }, + add_reviewer = { lhs = "va", desc = "add reviewer" }, + remove_reviewer = { lhs = "vd", desc = "remove reviewer request" }, + close_issue = { lhs = "ic", desc = "close PR" }, + reopen_issue = { lhs = "io", desc = "reopen PR" }, + list_issues = { lhs = "il", desc = "list open issues on same repo" }, reload = { lhs = "", desc = "reload PR" }, open_in_browser = { lhs = "", desc = "open PR in browser" }, copy_url = { lhs = "", desc = "copy url to system clipboard" }, goto_file = { lhs = "gf", desc = "go to file" }, - add_assignee = { lhs = "aa", desc = "add assignee" }, - remove_assignee = { lhs = "ad", desc = "remove assignee" }, - create_label = { lhs = "lc", desc = "create label" }, - add_label = { lhs = "la", desc = "add label" }, - remove_label = { lhs = "ld", desc = "remove label" }, - goto_issue = { lhs = "gi", desc = "navigate to a local repo issue" }, - add_comment = { lhs = "ca", desc = "add comment" }, - delete_comment = { lhs = "cd", desc = "delete comment" }, + add_assignee = { lhs = "aa", desc = "add assignee" }, + remove_assignee = { lhs = "ad", desc = "remove assignee" }, + create_label = { lhs = "lc", desc = "create label" }, + add_label = { lhs = "la", desc = "add label" }, + remove_label = { lhs = "ld", desc = "remove label" }, + goto_issue = { lhs = "gi", desc = "navigate to a local repo issue" }, + add_comment = { lhs = "ca", desc = "add comment" }, + delete_comment = { lhs = "cd", desc = "delete comment" }, next_comment = { lhs = "]c", desc = "go to next comment" }, prev_comment = { lhs = "[c", desc = "go to previous comment" }, - react_hooray = { lhs = "rp", desc = "add/remove 🎉 reaction" }, - react_heart = { lhs = "rh", desc = "add/remove ❤️ reaction" }, - react_eyes = { lhs = "re", desc = "add/remove 👀 reaction" }, - react_thumbs_up = { lhs = "r+", desc = "add/remove 👍 reaction" }, - react_thumbs_down = { lhs = "r-", desc = "add/remove 👎 reaction" }, - react_rocket = { lhs = "rr", desc = "add/remove 🚀 reaction" }, - react_laugh = { lhs = "rl", desc = "add/remove 😄 reaction" }, - react_confused = { lhs = "rc", desc = "add/remove 😕 reaction" }, - review_start = { lhs = "vs", desc = "start a review for the current PR" }, - review_resume = { lhs = "vr", desc = "resume a pending review for the current PR" }, + react_hooray = { lhs = "rp", desc = "add/remove 🎉 reaction" }, + react_heart = { lhs = "rh", desc = "add/remove ❤️ reaction" }, + react_eyes = { lhs = "re", desc = "add/remove 👀 reaction" }, + react_thumbs_up = { lhs = "r+", desc = "add/remove 👍 reaction" }, + react_thumbs_down = { lhs = "r-", desc = "add/remove 👎 reaction" }, + react_rocket = { lhs = "rr", desc = "add/remove 🚀 reaction" }, + react_laugh = { lhs = "rl", desc = "add/remove 😄 reaction" }, + react_confused = { lhs = "rc", desc = "add/remove 😕 reaction" }, + review_start = { lhs = "vs", desc = "start a review for the current PR" }, + review_resume = { lhs = "vr", desc = "resume a pending review for the current PR" }, }, review_thread = { - goto_issue = { lhs = "gi", desc = "navigate to a local repo issue" }, - add_comment = { lhs = "ca", desc = "add comment" }, - add_suggestion = { lhs = "sa", desc = "add suggestion" }, - delete_comment = { lhs = "cd", desc = "delete comment" }, + goto_issue = { lhs = "gi", desc = "navigate to a local repo issue" }, + add_comment = { lhs = "ca", desc = "add comment" }, + add_suggestion = { lhs = "sa", desc = "add suggestion" }, + delete_comment = { lhs = "cd", desc = "delete comment" }, next_comment = { lhs = "]c", desc = "go to next comment" }, prev_comment = { lhs = "[c", desc = "go to previous comment" }, select_next_entry = { lhs = "]q", desc = "move to next changed file" }, @@ -243,14 +243,14 @@ function M.get_default_values() select_first_entry = { lhs = "[Q", desc = "move to first changed file" }, select_last_entry = { lhs = "]Q", desc = "move to last changed file" }, close_review_tab = { lhs = "", desc = "close review tab" }, - react_hooray = { lhs = "rp", desc = "add/remove 🎉 reaction" }, - react_heart = { lhs = "rh", desc = "add/remove ❤️ reaction" }, - react_eyes = { lhs = "re", desc = "add/remove 👀 reaction" }, - react_thumbs_up = { lhs = "r+", desc = "add/remove 👍 reaction" }, - react_thumbs_down = { lhs = "r-", desc = "add/remove 👎 reaction" }, - react_rocket = { lhs = "rr", desc = "add/remove 🚀 reaction" }, - react_laugh = { lhs = "rl", desc = "add/remove 😄 reaction" }, - react_confused = { lhs = "rc", desc = "add/remove 😕 reaction" }, + react_hooray = { lhs = "rp", desc = "add/remove 🎉 reaction" }, + react_heart = { lhs = "rh", desc = "add/remove ❤️ reaction" }, + react_eyes = { lhs = "re", desc = "add/remove 👀 reaction" }, + react_thumbs_up = { lhs = "r+", desc = "add/remove 👍 reaction" }, + react_thumbs_down = { lhs = "r-", desc = "add/remove 👎 reaction" }, + react_rocket = { lhs = "rr", desc = "add/remove 🚀 reaction" }, + react_laugh = { lhs = "rl", desc = "add/remove 😄 reaction" }, + react_confused = { lhs = "rc", desc = "add/remove 😕 reaction" }, }, submit_win = { approve_review = { lhs = "", desc = "approve review" }, @@ -259,12 +259,12 @@ function M.get_default_values() close_review_tab = { lhs = "", desc = "close review tab" }, }, review_diff = { - submit_review = { lhs = "vs", desc = "submit review" }, - discard_review = { lhs = "vd", desc = "discard review" }, - add_review_comment = { lhs = "ca", desc = "add a new review comment" }, - add_review_suggestion = { lhs = "sa", desc = "add a new review suggestion" }, - focus_files = { lhs = "e", desc = "move focus to changed file panel" }, - toggle_files = { lhs = "b", desc = "hide/show changed files panel" }, + submit_review = { lhs = "vs", desc = "submit review" }, + discard_review = { lhs = "vd", desc = "discard review" }, + add_review_comment = { lhs = "ca", desc = "add a new review comment" }, + add_review_suggestion = { lhs = "sa", desc = "add a new review suggestion" }, + focus_files = { lhs = "e", desc = "move focus to changed file panel" }, + toggle_files = { lhs = "b", desc = "hide/show changed files panel" }, next_thread = { lhs = "]t", desc = "move to next thread" }, prev_thread = { lhs = "[t", desc = "move to previous thread" }, select_next_entry = { lhs = "]q", desc = "move to next changed file" }, @@ -272,24 +272,24 @@ function M.get_default_values() select_first_entry = { lhs = "[Q", desc = "move to first changed file" }, select_last_entry = { lhs = "]Q", desc = "move to last changed file" }, close_review_tab = { lhs = "", desc = "close review tab" }, - toggle_viewed = { lhs = "", desc = "toggle viewer viewed state" }, + toggle_viewed = { lhs = "", desc = "toggle viewer viewed state" }, goto_file = { lhs = "gf", desc = "go to file" }, }, file_panel = { - submit_review = { lhs = "vs", desc = "submit review" }, - discard_review = { lhs = "vd", desc = "discard review" }, + submit_review = { lhs = "vs", desc = "submit review" }, + discard_review = { lhs = "vd", desc = "discard review" }, next_entry = { lhs = "j", desc = "move to next changed file" }, prev_entry = { lhs = "k", desc = "move to previous changed file" }, select_entry = { lhs = "", desc = "show selected changed file diffs" }, refresh_files = { lhs = "R", desc = "refresh changed files panel" }, - focus_files = { lhs = "e", desc = "move focus to changed file panel" }, - toggle_files = { lhs = "b", desc = "hide/show changed files panel" }, + focus_files = { lhs = "e", desc = "move focus to changed file panel" }, + toggle_files = { lhs = "b", desc = "hide/show changed files panel" }, select_next_entry = { lhs = "]q", desc = "move to next changed file" }, select_prev_entry = { lhs = "[q", desc = "move to previous changed file" }, select_first_entry = { lhs = "[Q", desc = "move to first changed file" }, select_last_entry = { lhs = "]Q", desc = "move to last changed file" }, close_review_tab = { lhs = "", desc = "close review tab" }, - toggle_viewed = { lhs = "", desc = "toggle viewer viewed state" }, + toggle_viewed = { lhs = "", desc = "toggle viewer viewed state" }, }, repo = {}, }, diff --git a/lua/octo/init.lua b/lua/octo/init.lua index 192858e0..a7484767 100644 --- a/lua/octo/init.lua +++ b/lua/octo/init.lua @@ -50,7 +50,7 @@ function M.configure_octo_buffer(bufnr) -- review diff buffers local current_review = reviews.get_current_review() if current_review and #current_review.threads > 0 then - current_review.layout:cur_file():place_signs() + current_review.layout:get_current_file():place_signs() end elseif buffer then -- issue/pr/reviewthread buffers @@ -72,6 +72,7 @@ end ---@param opts ReloadOpts ---@return nil function M.load_buffer(opts) + opts = opts or {} local bufnr = opts.bufnr or vim.api.nvim_get_current_buf() local cursor_pos = vim.api.nvim_win_get_cursor(0) local bufname = vim.fn.bufname(bufnr) diff --git a/lua/octo/mappings.lua b/lua/octo/mappings.lua index bfbde03d..4aecc74c 100644 --- a/lua/octo/mappings.lua +++ b/lua/octo/mappings.lua @@ -44,7 +44,7 @@ return { require("octo.commands").remove_user "reviewer" end, reload = function() - vim.cmd [[e!]] + require("octo.commands").reload() end, open_in_browser = function() require("octo.navigation").open_in_browser() @@ -142,40 +142,26 @@ return { end, select_next_entry = function() local layout = reviews.get_current_layout() - if layout and layout.file_panel:is_open() then - local file_idx = layout.file_idx % #layout.files + 1 - local file = layout.files[file_idx] - if file then - layout:set_file(file, config.values.reviews.focus) - end + if layout then + layout:select_next_file() end end, select_prev_entry = function() local layout = reviews.get_current_layout() - if layout and layout.file_panel:is_open() then - local file_idx = (layout.file_idx - 2) % #layout.files + 1 - local file = layout.files[file_idx] - if file then - layout:set_file(file, config.values.reviews.focus) - end + if layout then + layout:select_prev_file() end end, select_first_entry = function() local layout = reviews.get_current_layout() - if layout and layout.file_panel:is_open() then - local file = layout.files[1] - if file then - layout:set_file(file, config.values.reviews.focus) - end + if layout then + layout:select_first_file() end end, select_last_entry = function() local layout = reviews.get_current_layout() - if layout and layout.file_panel:is_open() then - local file = layout.files[#layout.files] - if file then - layout:set_file(file, config.values.reviews.focus) - end + if layout then + layout:select_last_file() end end, next_entry = function() @@ -195,7 +181,7 @@ return { if layout and layout.file_panel:is_open() then local file = layout.file_panel:get_file_at_cursor() if file then - layout:set_file(file, config.values.reviews.focus) + layout:set_current_file(file) end end end, @@ -222,14 +208,23 @@ return { end, approve_review = function() local current_review = reviews.get_current_review() + if not current_review then + return + end current_review:submit "APPROVE" end, comment_review = function() local current_review = reviews.get_current_review() + if not current_review then + return + end current_review:submit "COMMENT" end, request_changes = function() local current_review = reviews.get_current_review() + if not current_review then + return + end current_review:submit "REQUEST_CHANGES" end, toggle_viewed = function() diff --git a/lua/octo/model/octo-buffer.lua b/lua/octo/model/octo-buffer.lua index ad477b96..3042ee58 100644 --- a/lua/octo/model/octo-buffer.lua +++ b/lua/octo/model/octo-buffer.lua @@ -234,7 +234,7 @@ function OctoBuffer:render_threads(threads) self.ready = true end ----Confgiures the buffer +---Configures the buffer function OctoBuffer:configure() -- configure buffer vim.api.nvim_buf_call(self.bufnr, function() @@ -512,13 +512,17 @@ function OctoBuffer:do_add_thread_comment(comment_metadata) end ---Adds a new review comment thread to the current review. +---@return nil function OctoBuffer:do_add_new_thread(comment_metadata) --TODO: How to create a new thread on a line where there is already one local review = require("octo.reviews").get_current_review() + if not review then + return + end local layout = review.layout local pr = review.pull_request - local file = layout:cur_file() + local file = layout:get_current_file() if not file then utils.error "No file selected" return diff --git a/lua/octo/model/pull-request.lua b/lua/octo/model/pull-request.lua index 32d0ec31..d9a0f446 100644 --- a/lua/octo/model/pull-request.lua +++ b/lua/octo/model/pull-request.lua @@ -3,6 +3,9 @@ local gh = require "octo.gh" local M = {} +---https://docs.github.com/en/graphql/reference/enums#fileviewedstate +---@alias ViewedState "DISMISSED" | "VIEWED" | "UNVIEWED" + ---@class PullRequest ---@field repo string ---@field head_repo string @@ -16,7 +19,7 @@ local M = {} ---@field right Rev ---@field local_right boolean ---@field local_left boolean ----@field files table +---@field files {[string]: ViewedState} ---@field diff string local PullRequest = {} PullRequest.__index = PullRequest @@ -78,6 +81,7 @@ function PullRequest:get_diff(pr) end ---Fetch the changed files for a given PR +---@param callback fun(files: FileEntry[]): nil function PullRequest:get_changed_files(callback) local url = string.format("repos/%s/pulls/%d/files", self.repo, self.number) gh.run { diff --git a/lua/octo/reviews/file-entry.lua b/lua/octo/reviews/file-entry.lua index 06d2aea2..e0dcf8f1 100644 --- a/lua/octo/reviews/file-entry.lua +++ b/lua/octo/reviews/file-entry.lua @@ -32,7 +32,11 @@ M._null_buffer = {} ---@field right_binary boolean|nil ---@field left_bufid integer ---@field right_bufid integer +--- If this table is empty, the buffer is not ready to be displayed +--- If the file is actually empty for the revision, table will be filled with a single empty line ---@field left_lines string[] +--- If this table is empty, the buffer is not ready to be displayed +--- If the file is actually empty for the revision, table will be filled with a single empty line ---@field right_lines string[] ---@field left_winid number ---@field right_winid number @@ -40,7 +44,7 @@ M._null_buffer = {} ---@field right_comment_ranges table ---@field associated_bufs integer[] ---@field diffhunks string[] ----@field viewed_state string +---@field viewed_state ViewedState local FileEntry = {} FileEntry.__index = FileEntry @@ -72,6 +76,8 @@ function FileEntry:new(opt) right_comment_ranges = right_ranges, left_binary = opt.left_binary, right_binary = opt.right_binary, + left_lines = {}, + right_lines = {}, diffhunks = diffhunks, associated_bufs = {}, viewed_state = pr.files[opt.path], @@ -104,7 +110,6 @@ function FileEntry:toggle_viewed() if stderr and not utils.is_blank(stderr) then vim.api.nvim_err_writeln(stderr) elseif output then - --local resp = vim.fn.json_decode(output) self.viewed_state = next_state local current_review = require("octo.reviews").get_current_review() if current_review then @@ -119,53 +124,60 @@ end ---FileEntry finalizer function FileEntry:destroy() self:detach_buffers() + for _, bn in ipairs(self.associated_bufs) do - pcall(vim.api.nvim_buf_delete, bn, { force = true }) + if vim.api.nvim_buf_get_name(bn):match "octo://*" then + pcall(vim.api.nvim_buf_delete, bn, { force = true }) + else + signs.unplace(bn) + vim.api.nvim_buf_set_option(bn, "modifiable", true) + vim.api.nvim_buf_clear_namespace(bn, constants.OCTO_REVIEW_COMMENTS_NS, 0, -1) + end end end ---Get the window id for the alternative side of the provided buffer ----@param split string +---@param split OctoSplit ---@return integer function FileEntry:get_alternative_win(split) - if split:lower() == "left" then + if split == "left" then return self.right_winid - elseif split:lower() == "right" then - return self.left_winid end + + return self.left_winid end ---Get the buffer id for the alternative side of the provided buffer ----@param split string +---@param split OctoSplit ---@return integer function FileEntry:get_alternative_buf(split) - if split:lower() == "left" then + if split == "left" then return self.right_bufid - elseif split:lower() == "right" then - return self.left_bufid end + + return self.left_bufid end ---Get the window id for the side of the provided buffer ----@param split string +---@param split OctoSplit ---@return integer function FileEntry:get_win(split) - if split:lower() == "left" then + if split == "left" then return self.left_winid - elseif split:lower() == "right" then - return self.right_winid end + + return self.right_winid end ---Get the buffer id for the side of the provided buffer ----@param split string +---@param split OctoSplit ---@return integer function FileEntry:get_buf(split) - if split:lower() == "left" then + if split == "left" then return self.left_bufid - elseif split:lower() == "right" then - return self.right_bufid end + + return self.right_bufid end ---Fetch file content locally or from GitHub. @@ -173,6 +185,9 @@ function FileEntry:fetch() local right_path = self.path local left_path = self.path local current_review = require("octo.reviews").get_current_review() + if not current_review then + return + end local right_sha = current_review.layout.right.commit local left_sha = current_review.layout.left.commit local right_abbrev = current_review.layout.right:abbrev() @@ -208,10 +223,16 @@ function FileEntry:fetch() -- wait until we have both versions return vim.wait(conf.timeout, function() - return self.left_lines and self.right_lines + return self:is_ready_to_render() end) end +---Determines whether the file content has been loaded and the file is ready to render +---@return boolean +function FileEntry:is_ready_to_render() + return #self.left_lines > 0 and #self.right_lines > 0 +end + ---Load the buffers. ---@param left_winid integer ---@param right_winid integer @@ -328,6 +349,9 @@ end ---Update thread signs in diff buffers. function FileEntry:place_signs() local current_review = require("octo.reviews").get_current_review() + if not current_review then + return + end local review_level = current_review:get_level() local splits = { { @@ -407,12 +431,17 @@ end function M._create_buffer(opts) local current_review = require("octo.reviews").get_current_review() + if not current_review then + return + end local bufnr if opts.use_local then -- Use the file from the file system -- Pros: LSP powered -- Cons: we need to change to the commit branch bufnr = vim.fn.bufadd(opts.path) + -- vim.fn.bufadd creates the buffer as unlisted by default + vim.api.nvim_buf_set_option(bufnr, "buflisted", true) else bufnr = vim.api.nvim_create_buf(false, false) local bufname = @@ -480,17 +509,12 @@ end function M._configure_buffer(bufid) utils.apply_mappings("review_diff", bufid) - -- local conf = config.values - -- vim.cmd(string.format("nnoremap %s :OctoAddReviewComment", conf.mappings.review_thread.add_comment)) - -- vim.cmd(string.format("vnoremap %s :OctoAddReviewComment", conf.mappings.review_thread.add_comment)) - -- vim.cmd(string.format("nnoremap %s :OctoAddReviewSuggestion", conf.mappings.review_thread.add_suggestion)) - -- vim.cmd(string.format("vnoremap %s :OctoAddReviewSuggestion", conf.mappings.review_thread.add_suggestion)) end function M._detach_buffer(bufid) local conf = config.values - for _, lhs in pairs(conf.mappings.review_diff) do - pcall(vim.keymap.del, "n", lhs, { buffer = bufid }) + for _, mapping in pairs(conf.mappings.review_diff) do + pcall(vim.keymap.del, "n", mapping.lhs, { buffer = bufid }) end end diff --git a/lua/octo/reviews/file-panel.lua b/lua/octo/reviews/file-panel.lua index 4e3e002c..06e0bf7e 100644 --- a/lua/octo/reviews/file-panel.lua +++ b/lua/octo/reviews/file-panel.lua @@ -182,7 +182,7 @@ function FilePanel:highlight_file(file) for i, f in ipairs(self.files) do if f == file then pcall(vim.api.nvim_win_set_cursor, self.winid, { i + header_size, 0 }) - vim.api.nvim_buf_clear_highlight(self.bufid, constants.OCTO_FILE_PANEL_NS, 0, -1) + vim.api.nvim_buf_clear_namespace(self.bufid, constants.OCTO_FILE_PANEL_NS, 0, -1) vim.api.nvim_buf_add_highlight(self.bufid, constants.OCTO_FILE_PANEL_NS, "CursorLine", i + header_size - 1, 0, -1) end end @@ -198,7 +198,7 @@ function FilePanel:highlight_prev_file() if f == cur then local line = utils.clamp(i + header_size - 1, header_size + 1, #self.files + header_size) pcall(vim.api.nvim_win_set_cursor, self.winid, { line, 0 }) - vim.api.nvim_buf_clear_highlight(self.bufid, constants.OCTO_FILE_PANEL_NS, 0, -1) + vim.api.nvim_buf_clear_namespace(self.bufid, constants.OCTO_FILE_PANEL_NS, 0, -1) vim.api.nvim_buf_add_highlight(self.bufid, constants.OCTO_FILE_PANEL_NS, "CursorLine", line - 1, 0, -1) end end @@ -214,13 +214,18 @@ function FilePanel:highlight_next_file() if f == cur then local line = utils.clamp(i + header_size + 1, header_size, #self.files + header_size) pcall(vim.api.nvim_win_set_cursor, self.winid, { line, 0 }) - vim.api.nvim_buf_clear_highlight(self.bufid, constants.OCTO_FILE_PANEL_NS, 0, -1) + vim.api.nvim_buf_clear_namespace(self.bufid, constants.OCTO_FILE_PANEL_NS, 0, -1) vim.api.nvim_buf_add_highlight(self.bufid, constants.OCTO_FILE_PANEL_NS, "CursorLine", line - 1, 0, -1) end end end function FilePanel:render() + local current_review = require("octo.reviews").get_current_review() + if not current_review then + return + end + if not self.render_data then return end @@ -232,7 +237,6 @@ function FilePanel:render() self.render_data:add_hl(...) end - local current_review = require("octo.reviews").get_current_review() local conf = config.values local strlen = vim.fn.strlen local s = "Files changed" diff --git a/lua/octo/reviews/init.lua b/lua/octo/reviews/init.lua index 78a38440..ec8c3bcb 100644 --- a/lua/octo/reviews/init.lua +++ b/lua/octo/reviews/init.lua @@ -7,6 +7,8 @@ local thread_panel = require "octo.reviews.thread-panel" local window = require "octo.ui.window" local utils = require "octo.utils" +---@alias ReviewLevel "COMMIT" | "PR" + ---@class Review ---@field repo string ---@field number integer @@ -122,6 +124,31 @@ function Review:start_or_resume() end) end +---Register freshly fetched files as this review's files +---Selects and fetches the first unread files +---Defaults to the first file if all files are VIEWED +---@param files FileEntry[] +function Review:set_files_and_select_first(files) + local selected_file_idx + for idx, file in ipairs(files) do + if file.viewed_state ~= "VIEWED" then + selected_file_idx = idx + break + end + end + + if not selected_file_idx and #files > 0 then + selected_file_idx = 1 + end + + self.layout.files = files + if selected_file_idx then + files[selected_file_idx]:fetch() + self.layout.selected_file_idx = selected_file_idx + end + self.layout:update_files() +end + -- Updates layout to focus on a single commit function Review:focus_commit(right, left) local pr = self.pull_request @@ -133,12 +160,7 @@ function Review:focus_commit(right, left) } self.layout:open(self) local cb = function(files) - -- pre-fetch the first file - if #files > 0 then - files[1]:fetch() - end - self.layout.files = files - self.layout:update_files() + self:set_files_and_select_first(files) end if right == self.pull_request.right.commit and left == self.pull_request.left.commit then pr:get_changed_files(cb) @@ -161,7 +183,6 @@ function Review:initiate(opts) -- create the layout self.layout = Layout:new { - -- TODO: rename to left_rev and right_rev left = opts.left or pr.left, right = opts.right or pr.right, files = {}, @@ -169,12 +190,7 @@ function Review:initiate(opts) self.layout:open(self) pr:get_changed_files(function(files) - -- pre-fetch the first file - if #files > 0 then - files[1]:fetch() - end - self.layout.files = files - self.layout:update_files() + self:set_files_and_select_first(files) end) end @@ -199,10 +215,10 @@ function Review:discard() local delete_query = graphql("delete_pull_request_review_mutation", self.id) gh.run { args = { "api", "graphql", "-f", string.format("query=%s", delete_query) }, - cb = function(output, stderr) - if stderr and not utils.is_blank(stderr) then - vim.error(stderr) - elseif output then + cb = function(output_inner, stderr_inner) + if stderr_inner and not utils.is_blank(stderr_inner) then + vim.error(stderr_inner) + elseif output_inner then self.id = default_id self.threads = {} self.files = {} @@ -235,8 +251,9 @@ function Review:update_threads(threads) if self.layout then self.layout.file_panel:render() self.layout.file_panel:redraw() - if self.layout:cur_file() then - self.layout:cur_file():place_signs() + local file = self.layout:get_current_file() + if file then + file:place_signs() end end end @@ -307,7 +324,7 @@ function Review:add_comment(isSuggestion) return end - local file = self.layout:cur_file() + local file = self.layout:get_current_file() if not file then return end @@ -439,15 +456,16 @@ function Review:add_comment(isSuggestion) end end +---Get the review level, aka whether the review is at commit or PR level +---@return ReviewLevel function Review:get_level() - local review_level = "COMMIT" if self.layout.left.commit == self.pull_request.left.commit and self.layout.right.commit == self.pull_request.right.commit then - review_level = "PR" + return "PR" end - return review_level + return "COMMIT" end local M = {} @@ -458,15 +476,21 @@ M.Review = Review function M.add_review_comment(isSuggestion) local review = M.get_current_review() + if not review then + error "Could not find review" + end review:add_comment(isSuggestion) end function M.jump_to_pending_review_thread(thread) local current_review = M.get_current_review() + if not current_review then + return + end for _, file in ipairs(current_review.layout.files) do if thread.path == file.path then current_review.layout:ensure_layout() - current_review.layout:set_file(file) + current_review.layout:set_current_file(file) local win = file:get_win(thread.diffSide) if vim.api.nvim_win_is_valid(win) then local review_level = current_review:get_level() @@ -484,15 +508,19 @@ function M.jump_to_pending_review_thread(thread) end end +--- Get the current review according to the tab page +--- @return Review | nil function M.get_current_review() local current_tabpage = vim.api.nvim_get_current_tabpage() return M.reviews[tostring(current_tabpage)] end +--- Get the diff Layout of the review if any +--- @return Layout | nil function M.get_current_layout() local current_review = M.get_current_review() if current_review then - return current_review.layout + return M.get_current_review().layout end end diff --git a/lua/octo/reviews/layout.lua b/lua/octo/reviews/layout.lua index b8c80922..cd1fe2ce 100644 --- a/lua/octo/reviews/layout.lua +++ b/lua/octo/reviews/layout.lua @@ -24,7 +24,7 @@ local win_reset_opts = { ---@field left_winid integer ---@field right_winid integer ---@field files FileEntry[] ----@field file_idx integer +---@field selected_file_idx integer ---@field ready boolean local Layout = {} Layout.__index = Layout @@ -36,7 +36,7 @@ function Layout:new(opt) left = opt.left, right = opt.right, files = opt.files, - file_idx = 1, + selected_file_idx = 1, ready = false, } this.file_panel = FilePanel:new(this.files) @@ -50,9 +50,9 @@ function Layout:open(review) require("octo.reviews").reviews[tostring(self.tabpage)] = review self:init_layout() - local file = self:cur_file() + local file = self:get_current_file() if file then - self:set_file(file, config.values.reviews.focus) + self:set_current_file(file) else self:file_safeguard() end @@ -68,7 +68,7 @@ function Layout:close() if self.tabpage and vim.api.nvim_tabpage_is_valid(self.tabpage) then local pagenr = vim.api.nvim_tabpage_get_number(self.tabpage) - pcall(vim.cmd, "tabclose " .. pagenr) + pcall(vim.cmd.tabclose, pagenr) end end @@ -79,15 +79,21 @@ function Layout:init_layout() self.file_panel:open() end -function Layout:cur_file() +--- Get the currently selected file +--- Returns nil if no files are currently in the review +--- @return FileEntry | nil +function Layout:get_current_file() if #self.files > 0 then - return self.files[utils.clamp(self.file_idx, 1, #self.files)] + return self.files[utils.clamp(self.selected_file_idx, 1, #self.files)] end return nil end --- sets selected file -function Layout:set_file(file, focus) +--- Sets the currently selected file +--- Focus parameter is optional and defaults to the config value +--- @param file FileEntry +--- @param focus OctoSplit | nil +function Layout:set_current_file(file, focus) self:ensure_layout() if self:file_safeguard() or not file then return @@ -96,30 +102,31 @@ function Layout:set_file(file, focus) for i, f in ipairs(self.files) do if f == file then found = true - self.file_idx = i + self.selected_file_idx = i break end end if found then - if not file.left_lines or not file.right_lines then + if not file:is_ready_to_render() then local result = file:fetch() if not result then vim.api.nvim_err_writeln("Timeout fetching " .. file.path) return end end - local cur = self:cur_file() + local cur = self:get_current_file() if cur then cur:detach_buffers() end vim.cmd [[diffoff!]] - self.files[self.file_idx] = file + self.files[self.selected_file_idx] = file file:load_buffers(self.left_winid, self.right_winid) - -- highlight file in file panel - self.file_panel:highlight_file(self:cur_file()) + -- Highlight file in file panel + self.file_panel:highlight_file(self:get_current_file()) - -- set focus on specified window + -- Set focus on specified window + focus = focus or config.values.reviews.focus if focus == "right" then vim.api.nvim_set_current_win(self.right_winid) else @@ -133,11 +140,49 @@ function Layout:update_files() self.file_panel.files = self.files self.file_panel:render() self.file_panel:redraw() - local file = self:cur_file() - self:set_file(file, config.values.reviews.focus) + local file = self:get_current_file() + if file then + self:set_current_file(file) + end self.update_needed = false end +--- Select the prev file entry +--- Loops around to last if already at first +function Layout:select_prev_file() + if self.file_panel:is_open() then + local prev_file_idx = (self.selected_file_idx - 2) % #self.files + 1 + local file = self.files[prev_file_idx] + self:set_current_file(file) + end +end + +--- Select the next file entry +--- Loops around to first if already at last +function Layout:select_next_file() + if self.file_panel:is_open() then + local next_file_idx = self.selected_file_idx % #self.files + 1 + local file = self.files[next_file_idx] + self:set_current_file(file) + end +end + +--- Select the first file entry +function Layout:select_first_file() + if self.file_panel:is_open() then + local file = self.files[1] + self:set_current_file(file) + end +end + +--- Select the last file entry +function Layout:select_last_file() + if self.file_panel:is_open() then + local file = self.files[#self.files] + self:set_current_file(file) + end +end + ---Checks the state of the view layout. ---@return table function Layout:validate_layout() @@ -173,13 +218,11 @@ function Layout:recover_layout(state) vim.cmd "aboveleft vsp" self.left_winid = vim.api.nvim_get_current_win() self.file_panel:open() - --self:set_file(self:cur_file(), "right") elseif not state.right_win then vim.api.nvim_set_current_win(self.left_winid) vim.cmd "belowright vsp" self.right_winid = vim.api.nvim_get_current_win() self.file_panel:open() - --self:set_file(self:cur_file(), "left") end self.ready = true @@ -197,7 +240,7 @@ end ---@return boolean function Layout:file_safeguard() if #self.files == 0 then - local cur = self:cur_file() + local cur = self:get_current_file() if cur then cur:detach_buffers() end @@ -212,14 +255,14 @@ function Layout:on_enter() self:update_files() end - local file = self:cur_file() + local file = self:get_current_file() if file then file:attach_buffers() end end function Layout:on_leave() - local file = self:cur_file() + local file = self:get_current_file() if file then file:detach_buffers() end diff --git a/lua/octo/reviews/rev.lua b/lua/octo/reviews/rev.lua index b06d3bc5..20b759ce 100644 --- a/lua/octo/reviews/rev.lua +++ b/lua/octo/reviews/rev.lua @@ -23,10 +23,7 @@ function Rev:new(commit, head) end function Rev:abbrev() - if self.commit then - return self.commit:sub(1, 7) - end - return nil + return self.commit:sub(1, 7) end M.Rev = Rev diff --git a/lua/octo/reviews/thread-panel.lua b/lua/octo/reviews/thread-panel.lua index 946856e0..f54cb185 100644 --- a/lua/octo/reviews/thread-panel.lua +++ b/lua/octo/reviews/thread-panel.lua @@ -1,7 +1,6 @@ local OctoBuffer = require("octo.model.octo-buffer").OctoBuffer local utils = require "octo.utils" local vim = vim -local config = require "octo.config" local M = {} @@ -22,7 +21,7 @@ function M.show_review_threads(params) return end - local file = review.layout:cur_file() + local file = review.layout:get_current_file() if not file then -- cant find the changed file metadata return @@ -83,7 +82,7 @@ function M.show_review_threads(params) end vim.api.nvim_buf_call(thread_buffer.bufnr, function() vim.cmd [[diffoff!]] - pcall(vim.cmd, "normal ]c") + pcall(vim.cmd.normal, "]c") end) end end @@ -108,30 +107,44 @@ function M.hide_thread_buffer(split, file) end end +---Create a thread buffer +---@param threads any +---@param repo any +---@param number any +---@param side any +---@param path any +---@return OctoBuffer | nil function M.create_thread_buffer(threads, repo, number, side, path) local current_review = require("octo.reviews").get_current_review() + if not current_review then + return + end + if not vim.startswith(path, "/") then path = "/" .. path end local line = threads[1].originalStartLine ~= vim.NIL and threads[1].originalStartLine or threads[1].originalLine local bufname = string.format("octo://%s/review/%s/threads/%s%s:%d", repo, current_review.id, side, path, line) - local bufnr = vim.fn.bufnr(bufname) - local buffer - if bufnr == -1 then - bufnr = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr, bufname) - buffer = OctoBuffer:new { - bufnr = bufnr, - number = number, - repo = repo, - } - buffer:render_threads(threads) - buffer:render_signs() - elseif vim.api.nvim_buf_is_loaded(bufnr) then - buffer = octo_buffers[bufnr] - else - vim.api.nvim_buf_delete(bufnr, { force = true }) + local existing_bufnr = vim.fn.bufnr(bufname) + + if existing_bufnr ~= -1 then + if vim.api.nvim_buf_is_loaded(existing_bufnr) then + return octo_buffers[existing_bufnr] + end + + -- Weird situation, force delete buffer and start from scratch + vim.api.nvim_buf_delete(existing_bufnr, { force = true }) end + + local bufnr = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_name(bufnr, bufname) + local buffer = OctoBuffer:new { + bufnr = bufnr, + number = number, + repo = repo, + } + buffer:render_threads(threads) + buffer:render_signs() return buffer end diff --git a/lua/octo/ui/writers.lua b/lua/octo/ui/writers.lua index ace9fb3b..6cda0358 100644 --- a/lua/octo/ui/writers.lua +++ b/lua/octo/ui/writers.lua @@ -380,19 +380,15 @@ function M.write_details(bufnr, issue, update) -- reviewers local reviewers = {} local collect_reviewer = function(name, state) - --if vim.g.octo_viewer ~= name then if not reviewers[name] then - if not reviewers[name] then - reviewers[name] = { state } - else - local states = reviewers[name] - if not vim.tbl_contains(states, state) then - table.insert(states, state) - end - reviewers[name] = states + reviewers[name] = { state } + else + local states = reviewers[name] + if not vim.tbl_contains(states, state) then + table.insert(states, state) end + reviewers[name] = states end - -- end end local timeline_nodes = {} for _, item in ipairs(issue.timelineItems.nodes) do diff --git a/lua/octo/utils.lua b/lua/octo/utils.lua index 3b035076..e7b4129e 100644 --- a/lua/octo/utils.lua +++ b/lua/octo/utils.lua @@ -1,6 +1,5 @@ local config = require "octo.config" local constants = require "octo.constants" -local date = require "octo.date" local gh = require "octo.gh" local graphql = require "octo.gh.graphql" local _, Job = pcall(require, "plenary.job") @@ -8,6 +7,10 @@ local vim = vim local M = {} +---@class OctoRepo +---@field host string +---@field name string + local repo_id_cache = {} local repo_templates_cache = {} local repo_info_cache = {} @@ -232,6 +235,8 @@ function M.parse_remote_url(url, aliases) end end +---Parse local git remotes from git cli +---@return OctoRepo[] function M.parse_git_remote() local conf = config.values local aliases = conf.ssh_aliases @@ -254,9 +259,10 @@ function M.parse_git_remote() return remotes end ---- Returns first host and repo information found in a list of remote values ---- If no argument is provided, defaults to matching against config's default remote ---- @param remote table | nil list of local remotes to match against +---Returns first host and repo information found in a list of remote values +---If no argument is provided, defaults to matching against config's default remote +---@param remote table | nil list of local remotes to match against +---@return OctoRepo function M.get_remote(remote) local conf = config.values local remotes = M.parse_git_remote() @@ -375,9 +381,10 @@ function M.in_pr_branch(pr) local local_remote = local_branch_with_local_remote[1] local local_branch = local_branch_with_local_remote[2] - local local_repo = M.get_remote_name { local_remote } + -- Github repos are case insensitive, ignore case when comparing to local remotes + local local_repo = M.get_remote_name({ local_remote }):lower() - if local_repo == pr.head_repo and local_branch == pr.head_ref_name then + if local_repo == pr.head_repo:lower() and local_branch == pr.head_ref_name then return true end @@ -1308,7 +1315,7 @@ end ---@param events table list of events ---@param winnr number window id of preview window ---@param bufnrs table list of buffers where the preview window will remain visible ----@see |autocmd-events| +---@see autocmd-events function M.close_preview_autocmd(events, winnr, bufnrs) local augroup = vim.api.nvim_create_augroup("preview_window_" .. winnr, { clear = true, diff --git a/lua/tests/plenary/config_spec.lua b/lua/tests/plenary/config_spec.lua index 7cde9572..dc65568a 100644 --- a/lua/tests/plenary/config_spec.lua +++ b/lua/tests/plenary/config_spec.lua @@ -40,7 +40,7 @@ describe("Config module:", function() it("user defined mappings completely overrides defaults.", function() eq(merged_config.mappings.issue.close_issue.lhs, "") eq(merged_config.mappings.issue.close_issue.desc, "Close issue") - eq(merged_config.mappings.pull_request.merge_pr.lhs, "pm") + eq(merged_config.mappings.pull_request.merge_pr.lhs, "pm") eq(merged_config.mappings.pull_request.merge_pr.desc, "merge commit PR") end) end)