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
33 changes: 7 additions & 26 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,18 @@ jobs:
- name: Install Neovim ${{ matrix.neovim }}
run: |
set -euo pipefail

VERSION=${{ matrix.neovim }}

# All historic 0.x tags use nvim-linux64.tar.gz, everything newer uses -x86_64
if [[ "$VERSION" =~ ^v0\.[0-9]+\. ]]; then
FILENAME=nvim-linux64.tar.gz
else
FILENAME=nvim-linux-x86_64.tar.gz
fi

URL="https://github.com/neovim/neovim/releases/download/${VERSION}/${FILENAME}"
echo "Downloading $URL"

curl -fL -o nvim.tar.gz "$URL" # -f = fail on 4xx/5xx, -L = follow redirects

curl -fL -o nvim.tar.gz "$URL"
mkdir nvim-extract
tar -xzf nvim.tar.gz -C nvim-extract

DIR="$(ls nvim-extract | head -n1)" # should be nvim-linux64 or nvim-linux-x86_64
DIR="$(ls nvim-extract | head -n1)"
sudo mv "nvim-extract/$DIR" /opt/nvim
echo "/opt/nvim/bin" >> "$GITHUB_PATH"

Expand All @@ -55,32 +48,20 @@ jobs:
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y lua5.1 liblua5.1-0-dev luarocks
sudo apt-get install -y lua5.1 liblua5.1-0-dev luarocks make
luarocks --lua-version=5.1 --local install luacheck
luarocks --lua-version=5.1 --local install luacov
luarocks --lua-version=5.1 --local install luacov-reporter-lcov
echo "$HOME/.luarocks/bin" >> $GITHUB_PATH
echo "$HOME/.luarocks/bin" >> "$GITHUB_PATH"

- name: Run tests with coverage
- name: Run tests and generate coverage
run: |
eval "$(luarocks --lua-version=5.1 path)"
luarocks --lua-version=5.1 list # debug
nvim --headless -u tests/minimal_init.lua -c "luafile tests/run_cov.lua"

luacov -r lcov > lcov.info
sed -i 's|SF:.*/codex.nvim/codex.nvim/|SF:|g' lcov.info
head -n 10 lcov.info # debug

# Debug output
echo "=== first 20 lines of lcov.info ==="
head -n 20 lcov.info
make coverage

echo "=== all source-file entries ==="
grep '^SF:' lcov.info | sed -e 's/^SF://g' | sort | uniq | head -n 10

- name: Upload code coverage
uses: codecov/codecov-action@v4
with:
files: lcov.info # <-- new file
files: lcov.info
disable_search: true
token: ${{ secrets.CODECOV_TOKEN }}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
luacov.report.out
luacov.stats.out
9 changes: 4 additions & 5 deletions .luacheckrc
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
-- .luacheckrc
std = "luajit"
globals = { "vim" }
std = 'luajit'
globals = { 'vim', 'describe', 'it', 'before_each', 'after_each', 'pending', 'assert', 'eq' }
ignore = {
"plugin/*", -- plugin loader shim
'plugin/*', -- plugin loader shim
}

max_line_length = false -- or turn it off completely

max_line_length = false -- or turn it off completely
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# gh-dash.nvim
<img width="1477" alt="image" src="https://github.com/user-attachments/assets/84bffe05-a2c3-4bdb-9cbe-0ea0be0ea279" />


## A Neovim plugin integrating the open-source gh-dash TUI for the `gh` CLI ([gh-dash](https://github.com/dlvhdr/gh-dash/))

> Latest version: ![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/johnseth97/gh-dash.nvim?sort=semver)
Expand Down
145 changes: 87 additions & 58 deletions lua/gh_dash/init.lua
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
local vim = vim

local M = {}

local config = {
keymaps = {},
border = 'single',
custom_border = {
{}, -- Top left corner
{}, -- Top side
{}, -- Top right corner
{}, -- Right side
{}, -- Bottom right corner
{}, -- Bottom side
{}, -- Bottom left corner
{}, -- Left side
},
width = 0.8,
height = 0.8,
cmd = { 'gh', 'dash' },
Expand Down Expand Up @@ -33,6 +41,41 @@ function M.setup(user_config)
end
end

local styles = {
single = {
{ '╭', 'FloatBorder' },
{ '─', 'FloatBorder' },
{ '╮', 'FloatBorder' },
{ '│', 'FloatBorder' },
{ '╯', 'FloatBorder' },
{ '─', 'FloatBorder' },
{ '╰', 'FloatBorder' },
{ '│', 'FloatBorder' },
},
double = {
{ '╔', 'FloatBorder' },
{ '═', 'FloatBorder' },
{ '╗', 'FloatBorder' },
{ '║', 'FloatBorder' },
{ '╝', 'FloatBorder' },
{ '═', 'FloatBorder' },
{ '╚', 'FloatBorder' },
{ '║', 'FloatBorder' },
},
square = {
{ '┌', 'FloatBorder' },
{ '─', 'FloatBorder' },
{ '┐', 'FloatBorder' },
{ '│', 'FloatBorder' },
{ '┘', 'FloatBorder' },
{ '─', 'FloatBorder' },
{ '└', 'FloatBorder' },
{ '│', 'FloatBorder' },
},
custom = config.custom_border,
none = nil,
}

-- Create a floating window displaying the gh_dash buffer
local function open_window()
-- compute dimensions and position
Expand All @@ -43,40 +86,11 @@ local function open_window()
-- resolve border style (string or table)
local border = config.border
if type(border) == 'string' then
local styles = {
single = {
{ '╭', 'FloatBorder' },
{ '─', 'FloatBorder' },
{ '╮', 'FloatBorder' },
{ '│', 'FloatBorder' },
{ '╯', 'FloatBorder' },
{ '─', 'FloatBorder' },
{ '╰', 'FloatBorder' },
{ '│', 'FloatBorder' },
},
double = {
{ '╔', 'FloatBorder' },
{ '═', 'FloatBorder' },
{ '╗', 'FloatBorder' },
{ '║', 'FloatBorder' },
{ '╝', 'FloatBorder' },
{ '═', 'FloatBorder' },
{ '╚', 'FloatBorder' },
{ '║', 'FloatBorder' },
},
square = {
{ '┌', 'FloatBorder' },
{ '─', 'FloatBorder' },
{ '┐', 'FloatBorder' },
{ '│', 'FloatBorder' },
{ '┘', 'FloatBorder' },
{ '─', 'FloatBorder' },
{ '└', 'FloatBorder' },
{ '│', 'FloatBorder' },
},
none = nil,
}
border = styles[border] or styles.single
if border == 'none' then
border = 'none'
else
border = styles[border] or styles.single
end
end
-- open floating window
state.win = vim.api.nvim_open_win(state.buf, true, {
Expand All @@ -95,16 +109,22 @@ function M.open()
vim.api.nvim_set_current_win(state.win)
return
end
if not state.buf or not vim.api.nvim_buf_is_valid(state.buf) then
if not state.buf or not vim.api.nvim_buf_is_valid(state.buf) or vim.api.nvim_buf_get_option(state.buf, 'modified') then
-- create an unlisted scratch buffer for the terminal
state.buf = vim.api.nvim_create_buf(false, false)
-- buffer options
vim.api.nvim_buf_set_option(state.buf, 'bufhidden', 'hide')
vim.api.nvim_buf_set_option(state.buf, 'swapfile', false)
vim.api.nvim_buf_set_option(state.buf, 'filetype', 'gh_dash')
-- map <Esc> in terminal and normal modes to close the gh_dash window
vim.api.nvim_buf_set_keymap(state.buf, 't', '<Esc>', [[<C-\><C-n><cmd>lua require('gh_dash').close()<CR>]], { noremap = true, silent = true })
vim.api.nvim_buf_set_keymap(state.buf, 'n', '<Esc>', [[<cmd>lua require('gh_dash').close()<CR>]], { noremap = true, silent = true })
-- Escape backgrounds the window cleanly
-- Map <Esc> in terminal mode to hide the popup
vim.api.nvim_buf_set_keymap(
state.buf,
't',
'<Esc>',
[[<C-\><C-n><cmd>lua vim.defer_fn(function() require('gh_dash').toggle() end, 10)<CR>]],
{ noremap = true, silent = true }
)
end
open_window()
-- determine if config.cmd is a simple executable name (no args) for checking
Expand All @@ -120,40 +140,34 @@ function M.open()
-- if simple command and not found, handle auto-install or notify
if check_cmd and vim.fn.executable(check_cmd) == 0 then
if config.autoinstall then
if vim.fn.executable 'npm' == 1 then
if vim.fn.executable 'gh' == 1 then
-- install via npm in the floating terminal to show output
do
local shell_cmd = vim.o.shell or 'sh'
local cmd = {
shell_cmd,
'-c',
"echo 'Autoinstalling OpenAI gh_dash via npm...'; npm install -g @openai/gh_dash",
"echo 'Autoinstalling gh_dash via gh CLI extensions...'; gh extension install dlvhdr/gh-dash",
}
state.job = vim.fn.termopen(cmd, {
cwd = vim.loop.cwd(),
on_exit = function(_, exit_code)
on_exit = function(_, exit_code, _)
state.job = nil
if exit_code == 0 then
vim.notify('[gh_dash.nvim] gh_dash CLI installed successfully', vim.log.levels.INFO)
-- automatically re-launch gh_dash CLI now that it's installed
vim.schedule(function()
M.close()
state.buf = nil
M.open()
end)
else
vim.notify('[gh_dash.nvim] failed to install gh_dash CLI', vim.log.levels.ERROR)
end
state.job = nil
end,
})
end
else
-- show installation instructions in the gh_dash popup
local msg = {
'npm not found; cannot auto-install gh_dash CLI.',
'gh CLI not found; cannot auto-install gh_dash extension.',
'',
'Please install via your system package manager, or manually run:',
' npm install -g @openai/gh_dash',
'Please install the gh CLI via your system package manager',
'i.e. `brew install gh`',
}
vim.api.nvim_buf_set_lines(state.buf, 0, -1, false, msg)
end
Expand All @@ -163,7 +177,7 @@ function M.open()
'gh_dash CLI not found.',
'',
'Install with:',
' npm install -g @openai/gh_dash',
'gh extension install dlvhdr/gh-dash',
'',
'Or enable autoinstall in your plugin setup:',
' require("gh_dash").setup{ autoinstall = true }',
Expand All @@ -175,9 +189,14 @@ function M.open()
-- spawn the gh_dash CLI in the floating terminal buffer
if not state.job then
state.job = vim.fn.termopen(config.cmd, {
cwd = vim.loop.cwd(),
on_exit = function()
wd = vim.loop.cwd(),
on_exit = function(_, exit_code, _)
state.job = nil
if exit_code == 0 then
vim.schedule(function()
M.close()
end)
end
end,
})
end
Expand All @@ -188,12 +207,22 @@ function M.close()
vim.api.nvim_win_close(state.win, true)
state.win = nil
end
if state.buf and vim.api.nvim_buf_is_valid(state.buf) then
vim.api.nvim_buf_delete(state.buf, { force = true })
state.buf = nil
end
end

function M.toggle()
if state.win and vim.api.nvim_win_is_valid(state.win) then
M.close()
-- HIDE the window (don't kill the job)
vim.api.nvim_win_close(state.win, true)
state.win = nil
elseif state.buf and vim.api.nvim_buf_is_valid(state.buf) then
-- Reopen window into existing buffer
open_window()
else
-- Full open if everything is gone
M.open()
end
end
Expand All @@ -218,7 +247,7 @@ function M.status()
return M.statusline() ~= ''
end,
-- gear icon
icon = '',
icon = '',
-- default color (blue)
color = { fg = '#51afef' },
}
Expand Down
25 changes: 25 additions & 0 deletions makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Makefile for gh_dash.nvim testing and coverage
# Usage:
# make test - run unit tests
# make coverage - run tests + generate coverage (luacov + lcov.info)

# Force correct Lua version for Neovim (Lua 5.1)
LUAROCKS_ENV = eval "$(luarocks --lua-version=5.1 path)"

# Headless Neovim test runner
NVIM_TEST_CMD = nvim --headless -u tests/minimal_init.lua -c "PlenaryBustedDirectory tests/"

.PHONY: test coverage clean

test:
$(LUAROCKS_ENV) && $(NVIM_TEST_CMD)

coverage:
$(LUAROCKS_ENV) && nvim --headless -u tests/minimal_init.lua -c "luafile tests/run_cov.lua"
ls -lh luacov.stats.out
$(LUAROCKS_ENV) && luacov -t LcovReporter
@echo "Generated coverage report: lcov.info"

clean:
rm -f luacov.stats.out lcov.info
@echo "Cleaned coverage artifacts"
Loading
Loading