From 9cbbf5fbcc56423e9e56d10327891b7d8793435f Mon Sep 17 00:00:00 2001 From: Kesse Jones Date: Thu, 30 Jun 2022 14:14:07 -0300 Subject: [PATCH] feat(branch): added interactive list to select branches --- .../buffers/branch_select_view/init.lua | 56 ++++++++++++ lua/neogit/buffers/branch_select_view/ui.lua | 18 ++++ lua/neogit/lib/git/branch.lua | 24 ++++- lua/neogit/lib/git/cli.lua | 8 +- lua/neogit/popups/branch.lua | 90 ++++++++++++++++--- 5 files changed, 177 insertions(+), 19 deletions(-) create mode 100644 lua/neogit/buffers/branch_select_view/init.lua create mode 100644 lua/neogit/buffers/branch_select_view/ui.lua diff --git a/lua/neogit/buffers/branch_select_view/init.lua b/lua/neogit/buffers/branch_select_view/init.lua new file mode 100644 index 000000000..cfe2b78d6 --- /dev/null +++ b/lua/neogit/buffers/branch_select_view/init.lua @@ -0,0 +1,56 @@ +local Buffer = require("neogit.lib.buffer") +local ui = require 'neogit.buffers.branch_select_view.ui' + +local M = {} + +-- @class BranchSelectViewBuffer +-- @field branches the branches list +-- @field action action dispatched by line selection +-- @field buffer Buffer +-- @see Buffer +-- +--- Creates a new BranchSelectViewBuffer +-- @param branches +-- @param action +-- @return BranchSelectViewBuffer +function M.new(branches, action) + local instance = { + action = action, + branches = branches, + buffer = nil + } + + setmetatable(instance, { __index = M }) + + return instance +end + +function M:close() + self.buffer:close() + self.buffer = nil +end + +function M:open() + self.buffer = Buffer.create { + name = "NeogitBranchSelectView", + filetype = "NeogitBranchSelectView", + kind = "split", + mappings = { + n = { + [""] = function(buffer) + local current_line = buffer:get_current_line() + local branch_name = current_line[1] + if self.action then + self.action(branch_name) + end + self:close() + end, + } + }, + render = function() + return ui.View(self.branches) + end + } +end + +return M diff --git a/lua/neogit/buffers/branch_select_view/ui.lua b/lua/neogit/buffers/branch_select_view/ui.lua new file mode 100644 index 000000000..fa2758bca --- /dev/null +++ b/lua/neogit/buffers/branch_select_view/ui.lua @@ -0,0 +1,18 @@ +local Ui = require 'neogit.lib.ui' +local util = require 'neogit.lib.util' + +local row = Ui.row +local text = Ui.text +local map = util.map + +local M = {} + +function M.View(branches) + return map(branches, function(branch_name) + return row{ + text(branch_name) + } + end) +end + +return M diff --git a/lua/neogit/lib/git/branch.lua b/lua/neogit/lib/git/branch.lua index 51aeb7924..2fc159172 100644 --- a/lua/neogit/lib/git/branch.lua +++ b/lua/neogit/lib/git/branch.lua @@ -15,10 +15,18 @@ local function parse_branches(branches) return other_branches end -local function get_local_branches() +function M.get_local_branches() local branches = cli.branch .list - .call() + .call_sync() + + return parse_branches(branches) +end + +function M.get_remote_branches() + local branches = cli.branch + .remotes + .call_sync() return parse_branches(branches) end @@ -27,7 +35,7 @@ function M.get_all_branches() local branches = cli.branch .list .all - .call() + .call_sync() return parse_branches(branches) end @@ -64,7 +72,7 @@ function M.prompt_for_branch(options) end function M.checkout_local() - local branches = get_local_branches() + local branches = M.get_local_branches() a.util.scheduler() local chosen = M.prompt_for_branch(branches) @@ -111,4 +119,12 @@ function M.checkout_new() cli.interactive_git_cmd(tostring(cli.checkout.new_branch(name))) end +function M.current() + local branch_name = cli.branch.current.call_sync() + if #branch_name > 0 then + return branch_name[1] + end + return nil +end + return M diff --git a/lua/neogit/lib/git/cli.lua b/lua/neogit/lib/git/cli.lua index cd696268f..d806f8a17 100644 --- a/lua/neogit/lib/git/cli.lua +++ b/lua/neogit/lib/git/cli.lua @@ -120,6 +120,11 @@ local configurations = { return function (branch) return tbl.b(branch) end + end, + new_branch_with_start_point = function (tbl) + return function (branch, start_point) + return tbl.args(branch, start_point).b() + end end } }), @@ -195,6 +200,7 @@ local configurations = { remotes = '-r', current = '--show-current', very_verbose = '-vv', + move = '-m', }, aliases = { name = function (tbl) @@ -725,7 +731,6 @@ local cli = setmetatable({ -- from: https://stackoverflow.com/questions/48948630/lua-ansi-escapes-pattern local ansi_escape_sequence_pattern = "[\27\155][][()#;?%d]*[A-PRZcf-ntqry=><~]" local stdout = {} - local raw_stdout = {} local chan local skip_count = 0 @@ -735,7 +740,6 @@ local cli = setmetatable({ pty = true, width = 100, on_stdout = function(_, data) - table.insert(raw_stdout, data) local is_end = #data == 1 and data[1] == "" if is_end then return diff --git a/lua/neogit/popups/branch.lua b/lua/neogit/popups/branch.lua index 13aa271fd..d6b8f7866 100644 --- a/lua/neogit/popups/branch.lua +++ b/lua/neogit/popups/branch.lua @@ -4,6 +4,29 @@ local cli = require 'neogit.lib.git.cli' local popup = require('neogit.lib.popup') local branch = require('neogit.lib.git.branch') local operation = require('neogit.operations') +local BranchSelectViewBuffer = require 'neogit.buffers.branch_select_view' +local input = require('neogit.lib.input') + +local function format_branches(list) + local branches = {} + for _,name in ipairs(list) do + local name_formatted = name:match("^remotes/(.*)") or name + if not name_formatted:match('^(.*)/HEAD') then + table.insert(branches, name_formatted) + end + end + return branches +end + +local function parse_remote_branch_name(remote_name) + local offset = remote_name:find('/') + if not offset then return nil, nil end + + local remote = remote_name:sub(1, offset-1) + local branch_name = remote_name:sub(offset+1, remote_name:len()) + + return remote, branch_name +end function M.create() local p = popup.builder() @@ -13,27 +36,68 @@ function M.create() status.refresh(true) end)) :action("b", "checkout branch/revision", operation('checkout_branch', function () - branch.checkout() - status.refresh(true) + local branches = format_branches(branch.get_all_branches()) + BranchSelectViewBuffer.new(branches, function (selected_branch) + if selected_branch == '' then return end + + cli.checkout.branch(selected_branch).call_sync() + status.dispatch_refresh(true) + end):open() end)) :action("d", "delete local branch", operation('delete_branch', function () - branch.delete() - status.refresh(true) + local branches = branch.get_local_branches() + BranchSelectViewBuffer.new(branches, function (selected_branch) + cli.branch.delete.name(selected_branch).call_sync() + status.dispatch_refresh(true) + end):open() end)) :action("D", "delete local branch and remote", operation('delete_branch', function () - local branch = branch.delete() - if branch and branch ~= '' then - cli.interactive_git_cmd(tostring(cli.push.remote("origin").delete.to(branch))) - end - status.refresh(true) + local branches = format_branches(branch.get_remote_branches()) + BranchSelectViewBuffer.new(branches, function (selected_branch) + if selected_branch == '' then return end + + local remote, branch_name = parse_remote_branch_name(selected_branch) + if not remote or not branch_name then return end + + cli.branch.delete.name(branch_name).call_sync() + cli.push.remote(remote).delete.to(branch_name).call_sync() + status.dispatch_refresh(true) + end):open() end)) :action("l", "checkout local branch", operation('checkout_local-branch', function () - branch.checkout_local() - status.refresh(true) + local branches = branch.get_local_branches() + BranchSelectViewBuffer.new(branches, function (selected_branch) + if selected_branch == '' then return end + cli.checkout.branch(selected_branch).call_sync() + status.dispatch_refresh(true) + end):open() end)) :action("c", "checkout new branch", operation('checkout_create-branch', function () - branch.checkout_new() - status.refresh(true) + local branches = format_branches(branch.get_all_branches()) + BranchSelectViewBuffer.new(branches, function(selected_branch) + if selected_branch == '' then return end + + local name = input.get_user_input('branch > ') + if not name or name == '' then return end + + cli.checkout.new_branch_with_start_point(name, selected_branch).call_sync() + status.dispatch_refresh(true) + end):open() + end)) + :action("m", "rename branch", operation('rename_branch', function () + local current_branch = branch.current() or '' + local branches = branch.get_local_branches() + table.insert(branches, current_branch) + + BranchSelectViewBuffer.new(branches, function (selected_branch) + if selected_branch == '' then return end + + local new_name = input.get_user_input('new branch name > ') + if not new_name or new_name == '' then return end + + cli.branch.move.args(selected_branch, new_name).call_sync() + status.dispatch_refresh(true) + end):open() end)) :build()