Skip to content
Open
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
1 change: 1 addition & 0 deletions lua/octo/gh/queries.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1070,6 +1070,7 @@ query($endCursor: String) {
... on User {
id
login
name
}
... on Organization {
id
Expand Down
271 changes: 176 additions & 95 deletions lua/octo/pickers/fzf-lua/pickers/users.lua
Original file line number Diff line number Diff line change
@@ -1,119 +1,200 @@
---@diagnostic disable
local entry_maker = require "octo.pickers.fzf-lua.entry_maker"
local octo_config = require "octo.config"
local queries = require "octo.gh.queries"
local fzf = require "fzf-lua"
local gh = require "octo.gh"
local graphql = require "octo.gh.graphql"
local picker_utils = require "octo.pickers.fzf-lua.pickers.utils"
local utils = require "octo.utils"

return function(cb)
local formatted_users = {}
local orgs = {}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be scoped here or defined within function?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I use if for the inception picker of orgs.
if its an org, I need to open another picker with the org teams, so I keep the orgs on the module level so I can access it another time.
Basically this is the same thing that was before my PR, but now just for orgs and not for users.
for users I can keep everything I need in the results table and use with-nth option of fzf to not show the irrelevant data.


local function contents(prompt)
-- skip empty queries
if not prompt or prompt == "" or utils.is_blank(prompt) then
return {}
end
local query = graphql("users_query", prompt)
local output = gh.run {
args = { "api", "graphql", "--paginate", "-f", string.format("query=%s", query) },
mode = "sync",
}
if output then
local users = {}
local orgs = {}
local responses = utils.get_pages(output)
for _, resp in ipairs(responses) do
for _, user in ipairs(resp.data.search.nodes) do
if not user.teams then
-- regular user
if not vim.tbl_contains(vim.tbl_keys(users), user.login) then
users[user.login] = {
id = user.id,
login = user.login,
}
end
elseif user.teams and user.teams.totalCount > 0 then
-- organization, collect all teams
if not vim.tbl_contains(vim.tbl_keys(orgs), user.login) then
orgs[user.login] = {
id = user.id,
login = user.login,
teams = user.teams.nodes,
}
else
vim.list_extend(orgs[user.login].teams, user.teams.nodes)
local delimiter = "\t"

local fzf_opts = {
["--delimiter"] = delimiter,
["--with-nth"] = "3..",
}

local function format_display(thing, entity_type)
local str = thing.id .. delimiter .. entity_type .. delimiter
local display_login = entity_type == "org" and require("fzf-lua").utils.ansi_codes.magenta(thing.login) or thing.login
str = str .. display_login

if thing.name and thing.name ~= vim.NIL then
str = string.format("%s (%s)", str, thing.name)
end

return str
end

local function get_user_requester(prompt)
-- skip empty queries
if utils.is_blank(prompt) then
return {}
end
local query = graphql("users_query", prompt)
local output = gh.run {
args = { "api", "graphql", "--paginate", "-f", string.format("query=%s", query) },
mode = "sync",
}
if not output then
return {}
end
local users = {}
local end_idx = output:find "}{"
-- add a newline after }{ if it exists
if end_idx then
output = output:sub(1, end_idx) .. "\n" .. output:sub(end_idx + 1)
end
local jsons = vim.split(output, "\n", { plain = true })
-- parse each JSON object
for _, json_raw in ipairs(jsons) do
local responses = utils.get_pages(json_raw)
for _, resp in ipairs(responses) do
for _, user in ipairs(resp.data.search.nodes) do
if not user.teams then
-- regular user
if not vim.tbl_contains(vim.tbl_keys(users), user.login) then
users[user.login] = {
id = user.id,
login = user.login,
}
if user.name then
users[user.login].name = user.name
end
end
elseif user.teams and user.teams.totalCount > 0 then
Comment on lines +46 to +68
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does vim.json.decode not work here for some reason?

Copy link
Author

@mosheavni mosheavni Jun 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does vim.json.decode not work here for some reason?

yes good question!
there's a bug, also on telescope provider BTW, where my org is yielding an error.

the json is not separated, you'll see the response being {......}{......}:

on telescope:

[telescope] [WARN  10:18:18] /Users/mosheavni/.local/share/nvim/lazy/telescope.nvim/lua...to.nvim/lua/octo/utils.lua:790: Expected the end but found T_OBJ_BEGIN at character 6417

on fzf-lua (without my PR):

Error executing vim.schedule lua callback: /Users/mosheavni/Repos/octo.nvim/lua/octo/utils.lua:790: Expected the end but found T_OBJ_BEGIN at character 6297
stack traceback:
        [C]: in function 'decode'
        /Users/mosheavni/Repos/octo.nvim/lua/octo/utils.lua:790: in function 'get_pages'
        ...pos/octo.nvim/lua/octo/pickers/fzf-lua/pickers/users.lua:24: in function '__fn_reload'
        ...vni/.local/share/nvim/lazy/fzf-lua/lua/fzf-lua/shell.lua:187: in function 'fn'
        ...vni/.local/share/nvim/lazy/fzf-lua/lua/fzf-lua/shell.lua:77: in function <...vni/.local/share/nvim/lazy/fzf-lua/lua/fzf-lua/shell.lua:76>

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There might be another function in utils.lua that does this processing. It shouldn't exist only in this function

-- organization, collect orgs
if not vim.tbl_contains(vim.tbl_keys(orgs), user.login) then
orgs[user.id] = {
id = user.id,
login = user.login,
teams = user.teams.nodes,
}
else
vim.list_extend(orgs[user.login].teams, user.teams.nodes)
end
end
end
end
end

-- TODO highlight orgs?
local function format_display(thing)
return thing.id .. " " .. thing.login
end
local results = {}
-- process orgs with teams
for _, user in pairs(users) do
user.ordinal = format_display(user, "user")
table.insert(results, user.ordinal)
end
for _, org in pairs(orgs) do
org.login = string.format("%s (%d)", org.login, #org.teams)
org.ordinal = format_display(org, "org")
table.insert(results, org.ordinal)
end
return results
end

local results = {}
-- process orgs with teams
for _, user in pairs(users) do
user.ordinal = format_display(user)
formatted_users[user.ordinal] = user
table.insert(results, user.ordinal)
end
for _, org in pairs(orgs) do
org.login = string.format("%s (%d)", org.login, #org.teams)
org.ordinal = format_display(org)
formatted_users[org.ordinal] = org
table.insert(results, org.ordinal)
end
return results
else
return {}
end
local function get_users(query_name, node_name)
local repo = utils.get_remote_name()
local owner, name = utils.split_repo(repo)
local output = gh.api.graphql {
query = queries[query_name],
f = { owner = owner, name = name },
paginate = true,
jq = ".data.repository." .. node_name .. ".nodes",
opts = { mode = "sync" },
}
if utils.is_blank(output) then
return {}
end

fzf.fzf_live(
contents,
vim.tbl_deep_extend("force", picker_utils.dropdown_opts, {
fzf_opts = {
["--delimiter"] = "' '",
["--with-nth"] = "2..",
},
actions = {
["default"] = {
function(user_selected)
local user_entry = formatted_users[user_selected[1]]
if not user_entry.teams then
-- user
cb(user_entry.id)
else
local formatted_teams = {}
local team_titles = {}
local results = {}
local flattened = utils.get_flatten_pages(output)
for _, user in ipairs(flattened) do
user.ordinal = format_display(user, "user")
table.insert(results, user.ordinal)
end
return results
end

for _, team in ipairs(user_entry.teams) do
local team_entry = entry_maker.gen_from_team(team)
local function get_user_id_type(selection)
local spl = vim.split(selection[1], delimiter)
return spl[1], spl[2]
end

return function(cb)
local cfg = octo_config.values
if cfg.users == "search" then
return fzf.fzf_live(
get_user_requester,
vim.tbl_deep_extend("force", picker_utils.dropdown_opts, {
fzf_opts = fzf_opts,
actions = {
["default"] = {
function(user_selected)
local user_id, user_type = get_user_id_type(user_selected)
if user_type == "user" then
cb(user_id)
else
-- handle org
local formatted_teams = {}
local team_titles = {}

if team_entry ~= nil then
formatted_teams[team_entry.ordinal] = team_entry
table.insert(team_titles, team_entry.ordinal)
for _, team in ipairs(orgs[user_id].teams) do
local team_entry = entry_maker.gen_from_team(team)

if team_entry ~= nil then
formatted_teams[team_entry.ordinal] = team_entry
table.insert(team_titles, team_entry.ordinal)
end
end
end

fzf.fzf_exec(
team_titles,
vim.tbl_deep_extend("force", picker_utils.dropdown_opts, {
actions = {
["default"] = function(team_selected)
local team_entry = formatted_teams[team_selected[1]]
cb(team_entry.team.id)
end,
},
})
)
end
fzf.fzf_exec(
team_titles,
vim.tbl_deep_extend("force", picker_utils.dropdown_opts, {
actions = {
["default"] = function(team_selected)
local team_entry = formatted_teams[team_selected[1]]
if false then
cb(team_entry.team.id)
end
utils.error "Not implemented yet"
end,
},
})
)
end
end,
},
},
})
)
else
local users = {}
if cfg.users == "assignable" then
users = get_users("assignable_users", "assignableUsers")
elseif cfg.users == "mentionable" then
users = get_users("mentionable_users", "mentionableUsers")
else
utils.error("Invalid user selection mode: " .. cfg.users)
return
end
if #users == 0 then
utils.error(string.format("No %s users found.", cfg.users))
return
end
fzf.fzf_exec(
users,
vim.tbl_deep_extend("force", picker_utils.dropdown_opts, {
fzf_opts = fzf_opts,
actions = {
["default"] = function(user_selected)
local user_id, _ = get_user_id_type(user_selected)
cb(user_id)
end,
},
},
})
)
})
)
end
end
2 changes: 1 addition & 1 deletion lua/octo/pickers/telescope/provider.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1043,7 +1043,7 @@ local function get_user_requester()
args = { "api", "graphql", "--paginate", "-f", string.format("query=%s", query) },
mode = "sync",
}
if output then
if not output then
return {}
end

Expand Down