Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions lua/coderabbit/health.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
local M = {}

local utils = require("coderabbit.utils")

function M.check()
vim.health.start("coderabbit.nvim")

Expand Down Expand Up @@ -41,8 +43,8 @@ function M.check()
-- Authentication
local auth_raw = vim.fn.system({ binary, "auth", "status", "--agent" })
if vim.v.shell_error == 0 then
local ok, auth = pcall(vim.json.decode, auth_raw)
if ok and auth.authenticated then
local auth = utils.json_decode(auth_raw)
if auth and auth.authenticated then
local user = auth.user and auth.user.username or "unknown"
local org = auth.currentOrg and auth.currentOrg.name or "none"
vim.health.ok("Authenticated as " .. user .. " (org: " .. org .. ")")
Expand Down
6 changes: 4 additions & 2 deletions lua/coderabbit/history.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
local M = {}

local utils = require("coderabbit.utils")

local function format_time(timestamp)
if not timestamp then
return "unknown"
Expand All @@ -20,7 +22,7 @@ function M.format_entry(entry)
if ctx.review_type then
table.insert(parts, ctx.review_type)
end
table.insert(parts, string.format("%d finding%s", entry.finding_count, entry.finding_count == 1 and "" or "s"))
table.insert(parts, utils.pluralize(entry.finding_count, "finding"))
return table.concat(parts, " │ ")
end

Expand All @@ -29,7 +31,7 @@ function M.open()
local entries = storage.list()

if #entries == 0 then
vim.notify("CodeRabbit: No saved reviews yet", vim.log.levels.INFO)
utils.notify("No saved reviews yet")
return
end

Expand Down
8 changes: 3 additions & 5 deletions lua/coderabbit/parser.lua
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
local M = {}

local utils = require("coderabbit.utils")

--- Parse a single NDJSON line from `cr review --agent` output.
--- Returns a table with at least a `type` field, or nil on parse failure.
function M.parse_line(line)
if not line or line == "" then
return nil
end
local ok, data = pcall(vim.json.decode, line)
if not ok or type(data) ~= "table" then
return nil
end
return data
return utils.json_decode(line)
end

--- Strip the boilerplate prefix from codegenInstructions.
Expand Down
6 changes: 4 additions & 2 deletions lua/coderabbit/quickfix.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
local M = {}

local utils = require("coderabbit.utils")

local severity_types = {
[vim.diagnostic.severity.ERROR] = "E",
[vim.diagnostic.severity.WARN] = "W",
Expand Down Expand Up @@ -56,15 +58,15 @@ function M.populate(id)
if id then
local entry = review.get_review(id)
if not entry then
vim.notify("CodeRabbit: Review #" .. id .. " not found", vim.log.levels.WARN)
utils.notify("Review #" .. id .. " not found", vim.log.levels.WARN)
return
end
findings = type(entry.findings) == "table" and entry.findings or {}
title = "CodeRabbit Review #" .. id
else
findings = review.get_results()
if #findings == 0 and not review.get_context() then
vim.notify("CodeRabbit: No review results. Run :CodeRabbitReview first", vim.log.levels.WARN)
utils.notify("No review results. Run :CodeRabbitReview first", vim.log.levels.WARN)
return
end
title = "CodeRabbit Review"
Expand Down
39 changes: 18 additions & 21 deletions lua/coderabbit/review.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ local config = require("coderabbit.config")
local cli = require("coderabbit.cli")
local parser = require("coderabbit.parser")
local diagnostics = require("coderabbit.diagnostics")
local utils = require("coderabbit.utils")

local spinner_frames = { "⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏" }
local FRAME_MS = 80
Expand Down Expand Up @@ -98,13 +99,13 @@ function M.run(opts)
opts = opts or {}

if M.is_running() then
vim.notify("CodeRabbit: Review already in progress", vim.log.levels.WARN)
utils.notify("Review already in progress", vim.log.levels.WARN)
return
end

if not cli.is_available() then
vim.notify(
"CodeRabbit: CLI not found. Install with: curl -fsSL https://cli.coderabbit.ai/install.sh | sh",
utils.notify(
"CLI not found. Install with: curl -fsSL https://cli.coderabbit.ai/install.sh | sh",
vim.log.levels.ERROR
)
return
Expand All @@ -116,7 +117,7 @@ function M.run(opts)
local finding_count = 0
local got_error = false

vim.notify("CodeRabbit: Reviewing...")
utils.notify("Reviewing...")

state.start_time = os.time()
state.fidget_handle = fidget_start()
Expand Down Expand Up @@ -144,7 +145,7 @@ function M.run(opts)
local now = os.time()
if not state.last_notify_time or (now - state.last_notify_time) >= 20 then
state.last_notify_time = now
vim.notify("CodeRabbit: " .. msg, vim.log.levels.INFO)
utils.notify(msg)
end
end
elseif event.type == "finding" then
Expand All @@ -171,7 +172,7 @@ function M.run(opts)
if msg:match("[Aa]uth") then
msg = msg .. "\nRun: cr auth login"
end
vim.notify("CodeRabbit: " .. msg, vim.log.levels.ERROR)
utils.notify(msg, vim.log.levels.ERROR)
end
end,

Expand All @@ -182,7 +183,7 @@ function M.run(opts)

if code == -1 then
fidget_finish("timed out")
vim.notify("CodeRabbit: Review timed out", vim.log.levels.ERROR)
utils.notify("Review timed out", vim.log.levels.ERROR)
return
end

Expand All @@ -193,18 +194,14 @@ function M.run(opts)
msg = msg .. "\nRun: cr auth login"
end
fidget_finish("failed")
vim.notify("CodeRabbit: " .. msg, vim.log.levels.ERROR)
utils.notify(msg, vim.log.levels.ERROR)
return
end

if not got_error then
local summary = string.format(
"CodeRabbit: Review complete. %d finding%s. Run :CodeRabbitShow to view.",
finding_count,
finding_count == 1 and "" or "s"
)
fidget_finish(string.format("done — %d finding%s", finding_count, finding_count == 1 and "" or "s"))
vim.notify(summary, vim.log.levels.INFO)
local fc = utils.pluralize(finding_count, "finding")
utils.notify("Review complete. " .. fc .. ". Run :CodeRabbitShow to view.")
fidget_finish("done — " .. fc)
else
fidget_finish("done (with errors)")
end
Expand All @@ -229,16 +226,16 @@ function M.stop()
fidget_finish("cancelled")
cli.cancel(state.job_id)
state.job_id = nil
vim.notify("CodeRabbit: Review cancelled", vim.log.levels.INFO)
utils.notify("Review cancelled")
else
vim.notify("CodeRabbit: No review in progress", vim.log.levels.WARN)
utils.notify("No review in progress", vim.log.levels.WARN)
end
end

function M.restore(id)
local entries = storage.list()
if #entries == 0 then
vim.notify("CodeRabbit: No saved reviews found", vim.log.levels.WARN)
utils.notify("No saved reviews found", vim.log.levels.WARN)
return
end

Expand All @@ -248,7 +245,7 @@ function M.restore(id)

local review = storage.load(id)
if not review then
vim.notify("CodeRabbit: Review #" .. id .. " not found", vim.log.levels.WARN)
utils.notify("Review #" .. id .. " not found", vim.log.levels.WARN)
return
end

Expand All @@ -259,7 +256,7 @@ function M.restore(id)
diagnostics.set(finding.filepath, { finding.diagnostic })
end

vim.notify(string.format("CodeRabbit: Restored %d findings from review #%d", #findings, id), vim.log.levels.INFO)
utils.notify(string.format("Restored %d findings from review #%d", #findings, id))
end)
end

Expand All @@ -272,7 +269,7 @@ function M.clear()
state.current_branch = nil
state.base_branch = nil
state.base_commit = nil
vim.notify("CodeRabbit: Cleared", vim.log.levels.INFO)
utils.notify("Cleared")
end

return M
10 changes: 5 additions & 5 deletions lua/coderabbit/show.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
local M = {}

local utils = require("coderabbit.utils")
local buf_id = nil

local severity_labels = vim.diagnostic.severity
Expand Down Expand Up @@ -174,7 +175,7 @@ function M.open(id)
if id then
local entry = review.get_review(id)
if not entry then
vim.notify("CodeRabbit: Review #" .. id .. " not found", vim.log.levels.WARN)
utils.notify("Review #" .. id .. " not found", vim.log.levels.WARN)
return
end
findings = entry.findings
Expand All @@ -187,7 +188,7 @@ function M.open(id)
end

if #findings == 0 and not running and not context then
vim.notify("CodeRabbit: No review results. Run :CodeRabbitReview first", vim.log.levels.WARN)
utils.notify("No review results. Run :CodeRabbitReview first", vim.log.levels.WARN)
return
end

Expand All @@ -198,9 +199,8 @@ function M.open(id)
content,
1,
string.format(
"> **Review in progress...** Showing %d finding%s so far. Run `:CodeRabbitShow` again to refresh.",
#findings,
#findings == 1 and "" or "s"
"> **Review in progress...** Showing %s so far. Run `:CodeRabbitShow` again to refresh.",
utils.pluralize(#findings, "finding")
)
)
end
Expand Down
29 changes: 10 additions & 19 deletions lua/coderabbit/storage.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
local M = {}

local utils = require("coderabbit.utils")
local base_dir = vim.fn.stdpath("data") .. "/coderabbit"

--- Override the base directory (for testing only).
Expand Down Expand Up @@ -79,13 +80,7 @@ function M.save(findings, context)
path = dir .. "/" .. filename
suffix = suffix + 1
end
local file = io.open(path, "w")
if not file then
return nil
end
local ok = file:write(json)
file:close()
if not ok then
if not utils.write_file(path, json) then
return nil
end
return filename
Expand All @@ -103,12 +98,10 @@ function M.list()

local entries = {}
for i, path in ipairs(files) do
local file = io.open(path, "r")
if file then
local content = file:read("*a")
file:close()
local ok, data = pcall(vim.json.decode, content)
if ok and type(data) == "table" then
local content = utils.read_file(path)
if content then
local data = utils.json_decode(content)
if data then
table.insert(entries, {
id = i,
timestamp = data.timestamp,
Expand All @@ -134,14 +127,12 @@ function M.load(id)
if not path then
return nil
end
local file = io.open(path, "r")
if not file then
local content = utils.read_file(path)
if not content then
return nil
end
local content = file:read("*a")
file:close()
local ok, data = pcall(vim.json.decode, content)
if not ok or type(data) ~= "table" then
local data = utils.json_decode(content)
if not data then
return nil
end
data.id = id
Expand Down
58 changes: 58 additions & 0 deletions lua/coderabbit/utils.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
local M = {}

--- Safely decode a JSON string into a table.
--- @param raw string
--- @return table|nil
function M.json_decode(raw)
local ok, data = pcall(vim.json.decode, raw)
if not ok or type(data) ~= "table" then
return nil
end
return data
end

--- Read an entire file into a string.
--- @param path string
--- @return string|nil
function M.read_file(path)
local file = io.open(path, "r")
if not file then
return nil
end
local content = file:read("*a")
file:close()
return content
end

--- Write a string to a file, replacing its contents.
--- @param path string
--- @param content string
--- @return boolean success
function M.write_file(path, content)
local file = io.open(path, "w")
if not file then
return false
end
local ok = file:write(content)
file:close()
return ok ~= nil
end

--- Show a notification prefixed with "CodeRabbit: ".
--- @param msg string
--- @param level number|nil vim.log.levels value (default: INFO)
function M.notify(msg, level)
vim.notify("CodeRabbit: " .. msg, level or vim.log.levels.INFO)
end

--- Format a count with a word, adding "s" for plurals.
--- e.g. pluralize(1, "finding") -> "1 finding"
--- pluralize(3, "finding") -> "3 findings"
--- @param n number
--- @param word string
--- @return string
function M.pluralize(n, word)
return string.format("%d %s", n, n == 1 and word or word .. "s")
end

return M
Loading
Loading