diff --git a/autoload/SpaceVim/layers/core/statusline.vim b/autoload/SpaceVim/layers/core/statusline.vim index 475755e12..ec0032798 100644 --- a/autoload/SpaceVim/layers/core/statusline.vim +++ b/autoload/SpaceVim/layers/core/statusline.vim @@ -579,6 +579,8 @@ function! SpaceVim#layers#core#statusline#get(...) abort return '%#SpaceVim_statusline_a# Tasks manager %#SpaceVim_statusline_a_SpaceVim_statusline_b#' . s:lsep elseif &filetype ==# 'SpaceVimGitBranchManager' return '%#SpaceVim_statusline_a# Branch manager %#SpaceVim_statusline_a_SpaceVim_statusline_b#' . s:lsep + elseif &filetype ==# 'SpaceVimGitRemoteManager' + return '%#SpaceVim_statusline_a# Remote manager %#SpaceVim_statusline_a_SpaceVim_statusline_b#' . s:lsep elseif &filetype ==# 'SpaceVimPlugManager' return '%#SpaceVim_statusline_a#' . s:winnr(1) . '%#SpaceVim_statusline_a_SpaceVim_statusline_b#' . s:lsep \ . '%#SpaceVim_statusline_b# PlugManager %#SpaceVim_statusline_b_SpaceVim_statusline_c#' . s:lsep diff --git a/autoload/SpaceVim/layers/git.vim b/autoload/SpaceVim/layers/git.vim index ee2dba747..ec67ff5a6 100644 --- a/autoload/SpaceVim/layers/git.vim +++ b/autoload/SpaceVim/layers/git.vim @@ -129,6 +129,7 @@ function! SpaceVim#layers#git#config() abort call SpaceVim#mapping#space#def('nnoremap', ['g', 'V'], 'Git log %', 'git-log-of-current-file', 1) call SpaceVim#mapping#space#def('nnoremap', ['g', 'v'], 'Git log', 'git-log-of-current-repo', 1) call SpaceVim#mapping#space#def('nnoremap', ['g', 'm'], 'Git branch', 'branch-manager', 1) + call SpaceVim#mapping#space#def('nnoremap', ['g', 'r'], 'Git remote', 'remote-manager', 1) endif augroup spacevim_layer_git autocmd! diff --git a/bundle/git.vim/lua/git/command/remote.lua b/bundle/git.vim/lua/git/command/remote.lua index 4257df7aa..1aa3fbb07 100644 --- a/bundle/git.vim/lua/git/command/remote.lua +++ b/bundle/git.vim/lua/git/command/remote.lua @@ -38,6 +38,10 @@ local function on_exit(id, code, single) end function M.run(argv) + if #argv == 0 then + require('git.ui.remote').open() + return + end local cmd = { 'git', 'remote' } for _, v in ipairs(argv) do table.insert(cmd, v) diff --git a/bundle/git.vim/lua/git/ui/remote.lua b/bundle/git.vim/lua/git/ui/remote.lua new file mode 100644 index 000000000..009cfd63b --- /dev/null +++ b/bundle/git.vim/lua/git/ui/remote.lua @@ -0,0 +1,300 @@ +--============================================================================= +-- remote.lua --- remote manager +-- Copyright (c) 2016-2024 Wang Shidong & Contributors +-- Author: Wang Shidong < wsdjeg@outlook.com > +-- URL: https://spacevim.org +-- License: GPLv3 +--============================================================================= + +local M = {} + +local job = require('spacevim.api.job') +local log = require('git.log') + +-- script local valuables + +local show_help_infoo = false +local update_branch_list_jobid = -1 +local update_branch_list_name = '' +local update_branch_list_branches = {} +local update_branch_remote_list = {} +local updating_extra_text = ' (updating)' + +-- fetch remote: + +local fetch_remote_jobid = -1 +local fetch_remote_name = '' + +local help_info = { + '" Git remote manager quickhelp', + '" ============================', + '" : checkout branch', + '" f: fetch remote under cursor', + '" o: toggle display of branchs', +} + +local bufnr = -1 +local winid = -1 + +local bufname = '' + +-- This should not be a list of string. it should be a list of remote object. +-- +-- { +-- opened = boolean, default false +-- name = string, the remote name +-- url = '' +-- branches = {}, list of string +-- updating = boolean +-- } +local remotes = {} + +-- the job to update remote list. +local list_remote_job_id = -1 + +local function on_stdout(id, data) + if id ~= list_remote_job_id then + return + end + + for _, v in ipairs(data) do + table.insert(remotes, { + opened = false, + name = v, + url = '', + branches = {}, + }) + table.insert(update_branch_remote_list, v) + end +end + +local function on_stderr(id, data) end + +local function update_buf_context() + if not vim.api.nvim_buf_is_valid(bufnr) then + return + end + local context = {} + if show_help_infoo then + for _, v in ipairs(help_info) do + table.insert(context, v) + end + end + table.insert(context, 'Git Remotes:') + for _, v in ipairs(remotes) do + local extra_text = '' + if v.updating then + extra_text = updating_extra_text + end + if v.opened then + table.insert(context, ' ▼ ' .. v.name .. extra_text) + for _, b in ipairs(v.branches) do + table.insert(context, '  ' .. b) + end + else + table.insert(context, ' ▷ ' .. v.name .. extra_text) + end + end + vim.api.nvim_buf_set_option(bufnr, 'modifiable', true) + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, context) + vim.api.nvim_buf_set_option(bufnr, 'modifiable', false) +end + +local function on_exit(id, code, signal) + if code == 0 and signal == 0 and vim.api.nvim_buf_is_valid(bufnr) then + update_buf_context() + end + if #update_branch_remote_list > 0 then + log.debug('update_branch_remote_list is:' .. vim.inspect(update_branch_remote_list)) + M.update_branch_list(table.remove(update_branch_remote_list)) + end +end + +local function update() + remotes = {} + local cmd = { 'git', 'remote' } + list_remote_job_id = job.start(cmd, { + on_stdout = on_stdout, + on_stderr = on_stderr, + on_exit = on_exit, + }) +end + +local function enter_win() end + +local function checkout_branch() end + +local function get_cursor_info() + local l = vim.fn.getline('.') + local c = {} + if vim.startswith(l, ' ▼ ') then + c.remote = string.sub(l, 7) + elseif vim.startswith(l, ' ▷ ') then + c.remote = string.sub(l, 7) + end + + if c.remote then + c.remote = string.gsub(c.remote, updating_extra_text, '') + end + + return c +end + +local function update_branch_stdout(id, data) + -- stdout example: + -- d89ff7896994692e7bcc6a53095c7ec2e2d780aarefs/heads/dein-lua-job + if id ~= update_branch_list_jobid then + return + end + + for _, v in ipairs(data) do + table.insert(update_branch_list_branches, string.sub(v, 53)) + end +end + +local function update_branch_exit(id, code, signal) + if id ~= update_branch_list_jobid then + return + end + update_branch_list_jobid = -1 + if code == 0 and signal == 0 then + for _, v in ipairs(remotes) do + if v.name == update_branch_list_name then + v.branches = update_branch_list_branches + v.updating = false + update_buf_context() + break + end + end + end + if #update_branch_remote_list > 0 then + M.update_branch_list(table.remove(update_branch_remote_list)) + end +end + +local function is_in_list(t, s) + for _, v in ipairs(t) do + if t == s then + return true + end + end + return false +end + +function M.update_branch_list(name) + if update_branch_list_jobid ~= -1 then + -- jobid is not -1 means job is running, check if the name same as current job, if it is not same as current job, insert to list. + if name ~= update_branch_list_name and not is_in_list(update_branch_remote_list, name) then + -- 此处应该检查list里是否包含name + table.insert(update_branch_remote_list, name) + end + return + end + update_branch_list_branches = {} + log.debug('start to update branch list for remote:' .. name) + update_branch_list_name = name + + update_branch_list_jobid = job.start({ 'git', 'ls-remote', '-h', update_branch_list_name }, { + on_stdout = update_branch_stdout, + on_exit = update_branch_exit, + }) + + log.debug('update_branch_list_jobid is:' .. update_branch_list_jobid) +end + +local function toggle_remote_branch() + local cursor_info = get_cursor_info() + if cursor_info.remote then + for _, v in ipairs(remotes) do + if v.name == cursor_info.remote then + v.opened = not v.opened + if v.opened and #v.branches == 0 then + -- remote tree is opened, but branch list is empty, the update the branch list. + -- + M.update_branch_list(v.name) + end + update_buf_context() + break + end + end + end +end + +local function toggle_help() + if show_help_infoo then + show_help_infoo = false + else + show_help_infoo = true + end + update_buf_context() +end + +-- functions to fetch remote: + +local function on_fetch_exit(id, code, signal) + if id == fetch_remote_jobid and code == 0 and signal == 0 then + M.update_branch_list(fetch_remote_name) + end + fetch_remote_name = '' + fetch_remote_jobid = -1 +end + +local function fetch_remote() + local cursor_info = get_cursor_info() + if cursor_info.remote then + fetch_remote_name = cursor_info.remote + fetch_remote_jobid = job.start({ 'git', 'fetch', cursor_info.remote }, { + on_exit = on_fetch_exit, + }) + if fetch_remote_jobid > 0 then + for _, v in ipairs(remotes) do + if v.name == fetch_remote_name then + v.updating = true + update_buf_context() + break + end + end + end + end +end + +function M.open() + if bufnr ~= -1 and vim.api.nvim_buf_is_valid(bufnr) then + vim.api.nvim_buf_delete(bufnr, { + force = true, + unload = false, + }) + end + vim.api.nvim_command('topleft vsplit __git_remote_manager__') + winid = vim.api.nvim_get_current_win() + local lines = vim.o.columns * 20 / 100 + vim.api.nvim_command('vertical resize ' .. tostring(lines)) + vim.api.nvim_command( + 'setlocal buftype=nofile bufhidden=wipe nobuflisted nolist noswapfile nowrap cursorline nospell nonu norelativenumber winfixheight nomodifiable winfixwidth' + ) + vim.api.nvim_command('set filetype=SpaceVimGitRemoteManager') + bufnr = vim.api.nvim_get_current_buf() + update() + local id = vim.api.nvim_create_augroup('spc_git_remote_manager', { + clear = true, + }) + vim.api.nvim_create_autocmd({ 'BufWipeout' }, { + group = id, + buffer = bufnr, + callback = enter_win, + }) + vim.api.nvim_buf_set_keymap(bufnr, 'n', '', '', { + callback = checkout_branch, + }) + vim.api.nvim_buf_set_keymap(bufnr, 'n', 'o', '', { + callback = toggle_remote_branch, + }) + vim.api.nvim_buf_set_keymap(bufnr, 'n', '?', '', { + callback = toggle_help, + }) + vim.api.nvim_buf_set_keymap(bufnr, 'n', 'f', '', { + callback = fetch_remote, + }) +end + +return M diff --git a/docs/cn/layers/git.md b/docs/cn/layers/git.md index d95e19c91..43f207026 100644 --- a/docs/cn/layers/git.md +++ b/docs/cn/layers/git.md @@ -50,6 +50,7 @@ lang: zh | `SPC g c` | 打开 git commit 窗口 | | `SPC g p` | 执行 git push | | `SPC g m` | git 分支管理 | +| `SPC g r` | git remote 管理 | | `SPC g d` | 打开 git diff 窗口 | | `SPC g A` | git add 所有文件 | | `SPC g b` | 打开 git blame 窗口 | diff --git a/docs/layers/git.md b/docs/layers/git.md index b640ef684..f69c39f40 100644 --- a/docs/layers/git.md +++ b/docs/layers/git.md @@ -49,6 +49,7 @@ if you want to use `fugitive` instead: | `SPC g c` | edit git commit | | `SPC g p` | git push | | `SPC g m` | git branch manager | +| `SPC g r` | git remote manager | | `SPC g d` | view git diff | | `SPC g A` | stage all files | | `SPC g b` | open git blame windows | diff --git a/syntax/SpaceVimGitRemoteManager.vim b/syntax/SpaceVimGitRemoteManager.vim new file mode 100644 index 000000000..3022686e2 --- /dev/null +++ b/syntax/SpaceVimGitRemoteManager.vim @@ -0,0 +1,13 @@ +scriptencoding utf-8 +sy match SPCGitRemoteName '^\s*[▷▼]\s.*' +sy match SPCGitRemoteTitle '^Git Remotes:' +sy match SPCGitRemoteHelpKey '" \zs[^:]*\ze[:]' +sy match SPCGitRemoteHelpTitle 'Git remote manager quickhelp' +sy match SPCGitRemoteHelp '^".*' contains=SPCGitRemoteHelpTitle,SPCGitRemoteHelpKey + +hi def link SPCGitRemoteName Directory +hi def link SPCGitRemoteHelpKey Identifier +hi def link SPCGitRemoteTitle Title +hi def link SPCGitRemoteHelpTitle Title +hi def link SPCGitRemoteHelp String +"