diff --git a/README.md b/README.md index c1c5d7d..f42f16e 100644 --- a/README.md +++ b/README.md @@ -94,3 +94,50 @@ If you have `neovim-remote` and don't want `lazygit.nvim` to use it, you can dis ```vim let g:lazygit_use_neovim_remote = 0 ``` + +### Telescope Plugin + +The Telescope plugin is used to track all git repository visited in one nvim session. + +![lazygittelplugin](https://user-images.githubusercontent.com/10464534/156933468-c89abee4-6afb-457c-8b02-55b67913aef2.png) +(background image is not included :smirk:) + +**Why a telescope Plugin** ? + +Assuming you have one or more submodule(s) in your project and you want to commit changes in both the submodule(s) +and the main repo. +Though switching between submodules and main repo is not straight forward. +A solution at first could be: + +1. open a file inside the submodule +2. open lazygit +3. do commit +4. then open a file in the main repo +5. open lazygit +6. do commit + +That is really annoying. +Instead, you can open it with telescope. + +**How to use** + +To load the telescope extension you have to add this line to your configuration: + +```lua +require('telescope').load_extension('lazygit') +``` + +By default the paths of each repo is stored only when lazygit is triggered. +Though, this may not be convenient, so it possible to do something like this: + +```vim + autocmd BufEnter * :lua require('lazygit.utils').project_root_dir() +``` + +That makes sure that any opened buffer which is contained in a git repo will be tracked. + +Once you have loaded the extension, you can invoke the plugin using: + +```lua +lua require("telescope").extensions.lazygit_telescope.lazygit() +``` diff --git a/lua/lazygit.lua b/lua/lazygit.lua index 32cea0f..f1e0b2e 100644 --- a/lua/lazygit.lua +++ b/lua/lazygit.lua @@ -1,62 +1,25 @@ -vim = vim -local api = vim.api -local fn = vim.fn +local open_floating_window = require"lazygit.window".open_floating_window +local project_root_dir = require"lazygit.utils".project_root_dir +local is_lazygit_available = require"lazygit.utils".is_lazygit_available +local is_symlink = require"lazygit.utils".is_symlink +local fn = vim.fn LAZYGIT_BUFFER = nil LAZYGIT_LOADED = false vim.g.lazygit_opened = 0 ---- Strip leading and lagging whitespace -local function trim(str) - return str:gsub('^%s+', ''):gsub('%s+$', '') -end - ---- Check if lazygit is available -local function is_lazygit_available() - return fn.executable('lazygit') == 1 -end - -local function is_symlink() - local resolved = fn.resolve(fn.expand('%:p')) - return resolved ~= fn.expand('%:p') -end - ---- Get project_root_dir for git repository -local function project_root_dir() - -- always use bash on Unix based systems. - local oldshell = vim.o.shell - if vim.fn.has('win32') == 0 then - vim.o.shell = 'bash' - end - - -- try symlinked file location instead - local gitdir = fn.system( - 'cd "' .. fn.fnamemodify(fn.resolve(fn.expand('%:p')), ':h') .. '" && git rev-parse --show-toplevel') - local isgitdir = fn.matchstr(gitdir, '^fatal:.*') == '' - - if isgitdir then - vim.o.shell = oldshell - return trim(gitdir) - end - - -- revert to old shell - vim.o.shell = oldshell - - -- just return current working directory - return fn.getcwd(0, 0) -end - --- on_exit callback function to delete the open buffer when lazygit exits in a neovim terminal local function on_exit(job_id, code, event) - if code == 0 then - -- Close the window where the LAZYGIT_BUFFER is - vim.cmd('silent! :q') - LAZYGIT_BUFFER = nil - LAZYGIT_LOADED = false - vim.g.lazygit_opened = 0 - vim.cmd('silent! :checktime') + if code ~= 0 then + return end + + vim.cmd('silent! :q') + LAZYGIT_BUFFER = nil + LAZYGIT_LOADED = false + vim.g.lazygit_opened = 0 + vim.cmd('silent! :checktime') end --- Call lazygit @@ -69,84 +32,6 @@ local function exec_lazygit_command(cmd) vim.cmd 'startinsert' end ---- open floating window with nice borders -local function open_floating_window() - local floating_window_scaling_factor = vim.g.lazygit_floating_window_scaling_factor - - -- Why is this required? - -- vim.g.lazygit_floating_window_scaling_factor returns different types if the value is an integer or float - if type(floating_window_scaling_factor) == 'table' then - floating_window_scaling_factor = floating_window_scaling_factor[false] - end - - local status, plenary = pcall(require, 'plenary.window.float') - if status and vim.g.lazygit_floating_window_use_plenary and vim.g.lazygit_floating_window_use_plenary ~= 0 then - plenary.percentage_range_window(floating_window_scaling_factor, floating_window_scaling_factor) - return - end - - local height = math.ceil(vim.o.lines * floating_window_scaling_factor) - 1 - local width = math.ceil(vim.o.columns * floating_window_scaling_factor) - - local row = math.ceil(vim.o.lines - height) / 2 - local col = math.ceil(vim.o.columns - width) / 2 - - local border_opts = { - style = 'minimal', - relative = 'editor', - row = row - 1, - col = col - 1, - width = width + 2, - height = height + 2, - } - - local opts = { style = 'minimal', relative = 'editor', row = row, col = col, width = width, height = height } - - local topleft, topright, botleft, botright - local corner_chars = vim.g.lazygit_floating_window_corner_chars - if type(corner_chars) == 'table' and #corner_chars == 4 then - topleft, topright, botleft, botright = unpack(corner_chars) - else - topleft, topright, botleft, botright = '╭', '╮', '╰', '╯' - end - - local border_lines = { topleft .. string.rep('─', width) .. topright } - local middle_line = '│' .. string.rep(' ', width) .. '│' - for i = 1, height do - table.insert(border_lines, middle_line) - end - table.insert(border_lines, botleft .. string.rep('─', width) .. botright) - - -- create a unlisted scratch buffer for the border - local border_buffer = api.nvim_create_buf(false, true) - - -- set border_lines in the border buffer from start 0 to end -1 and strict_indexing false - api.nvim_buf_set_lines(border_buffer, 0, -1, true, border_lines) - -- create border window - local border_window = api.nvim_open_win(border_buffer, true, border_opts) - vim.cmd('set winhl=Normal:Floating') - - -- create a unlisted scratch buffer - if LAZYGIT_BUFFER == nil or vim.fn.bufwinnr(LAZYGIT_BUFFER) == -1 then - LAZYGIT_BUFFER = api.nvim_create_buf(false, true) - else - LAZYGIT_LOADED = true - end - -- create file window, enter the window, and use the options defined in opts - local _ = api.nvim_open_win(LAZYGIT_BUFFER, true, opts) - - vim.bo[LAZYGIT_BUFFER].filetype = 'lazygit' - - vim.cmd('setlocal bufhidden=hide') - vim.cmd('setlocal nocursorcolumn') - vim.cmd('set winblend=' .. vim.g.lazygit_floating_window_winblend) - - -- use autocommand to ensure that the border_buffer closes at the same time as the main buffer - local cmd = [[autocmd WinLeave silent! execute 'hide']] - vim.cmd(cmd) - cmd = [[autocmd WinLeave silent! execute 'silent bdelete! %s']] - vim.cmd(cmd:format(border_buffer)) -end --- :LazyGit entry point local function lazygit(path) @@ -158,6 +43,7 @@ local function lazygit(path) open_floating_window() local cmd = 'lazygit' + -- set path to the root path _ = project_root_dir() @@ -166,7 +52,9 @@ local function lazygit(path) path = project_root_dir() end else - cmd = cmd .. ' -p ' .. path + if fn.isdirectory(path) then + cmd = cmd .. ' -p ' .. path + end end exec_lazygit_command(cmd) diff --git a/lua/lazygit/utils.lua b/lua/lazygit/utils.lua new file mode 100644 index 0000000..a8ace4d --- /dev/null +++ b/lua/lazygit/utils.lua @@ -0,0 +1,101 @@ +local fn = vim.fn + +-- store all git repositories visited in this session +local lazygit_visited_git_repos = {} + +-- TODO:check if the repo isa git repo +local function append_git_repo_path(repo_path) + if repo_path == nil or not fn.isdirectory(repo_path) then + return + end + + for _, path in ipairs(lazygit_visited_git_repos) do + if path == repo_path then + return + end + end + + table.insert(lazygit_visited_git_repos, tostring(repo_path)) +end + + +--- Strip leading and lagging whitespace +local function trim(str) + return str:gsub('^%s+', ''):gsub('%s+$', '') +end + + +local function get_root() + local cwd = vim.loop.cwd() + local status, job = pcall(require, 'plenary.job') + if not status then + return fn.system('git rev-parse --show-toplevel') + end + + local gitroot_job = job:new({ + 'git', + 'rev-parse', + '--show-toplevel', + cwd=cwd + }) + + local path, code = gitroot_job:sync() + if (code ~= 0) then + return nil + end + + return table.concat(path, "") +end + +--- Get project_root_dir for git repository +local function project_root_dir() + -- always use bash on Unix based systems. + local oldshell = vim.o.shell + if vim.fn.has('win32') == 0 then + vim.o.shell = 'bash' + end + + local root = get_root() + if root == nil then + return nil + end + + local cmd = string.format('cd "%s" && git rev-parse --show-toplevel', fn.fnamemodify(fn.resolve(fn.expand('%:p')), ':h'), root) + -- try symlinked file location instead + local gitdir = fn.system(cmd) + local isgitdir = fn.matchstr(gitdir, '^fatal:.*') == '' + + if isgitdir then + vim.o.shell = oldshell + append_git_repo_path(gitdir) + return trim(gitdir) + end + + -- revert to old shell + vim.o.shell = oldshell + + local repo_path = fn.getcwd(0, 0) + append_git_repo_path(repo_path) + + -- just return current working directory + return repo_path +end + +--- Check if lazygit is available +local function is_lazygit_available() + return fn.executable('lazygit') == 1 +end + +local function is_symlink() + local resolved = fn.resolve(fn.expand('%:p')) + return resolved ~= fn.expand('%:p') +end + + +return { + get_root = get_root, + project_root_dir = project_root_dir, + lazygit_visited_git_repos = lazygit_visited_git_repos, + is_lazygit_available = is_lazygit_available, + is_symlink = is_symlink, +} diff --git a/lua/lazygit/window.lua b/lua/lazygit/window.lua new file mode 100644 index 0000000..9d2a5eb --- /dev/null +++ b/lua/lazygit/window.lua @@ -0,0 +1,85 @@ +local api = vim.api + + +--- open floating window with nice borders +local function open_floating_window() + local floating_window_scaling_factor = vim.g.lazygit_floating_window_scaling_factor + + -- Why is this required? + -- vim.g.lazygit_floating_window_scaling_factor returns different types if the value is an integer or float + if type(floating_window_scaling_factor) == 'table' then + floating_window_scaling_factor = floating_window_scaling_factor[false] + end + + local status, plenary = pcall(require, 'plenary.window.float') + if status and vim.g.lazygit_floating_window_use_plenary and vim.g.lazygit_floating_window_use_plenary ~= 0 then + plenary.percentage_range_window(floating_window_scaling_factor, floating_window_scaling_factor) + return + end + + local height = math.ceil(vim.o.lines * floating_window_scaling_factor) - 1 + local width = math.ceil(vim.o.columns * floating_window_scaling_factor) + + local row = math.ceil(vim.o.lines - height) / 2 + local col = math.ceil(vim.o.columns - width) / 2 + + local border_opts = { + style = 'minimal', + relative = 'editor', + row = row - 1, + col = col - 1, + width = width + 2, + height = height + 2, + } + + local opts = { style = 'minimal', relative = 'editor', row = row, col = col, width = width, height = height } + + local topleft, topright, botleft, botright + local corner_chars = vim.g.lazygit_floating_window_corner_chars + if type(corner_chars) == 'table' and #corner_chars == 4 then + topleft, topright, botleft, botright = unpack(corner_chars) + else + topleft, topright, botleft, botright = '╭', '╮', '╰', '╯' + end + + local border_lines = { topleft .. string.rep('─', width) .. topright } + local middle_line = '│' .. string.rep(' ', width) .. '│' + for i = 1, height do + table.insert(border_lines, middle_line) + end + table.insert(border_lines, botleft .. string.rep('─', width) .. botright) + + -- create a unlisted scratch buffer for the border + local border_buffer = api.nvim_create_buf(false, true) + + -- set border_lines in the border buffer from start 0 to end -1 and strict_indexing false + api.nvim_buf_set_lines(border_buffer, 0, -1, true, border_lines) + -- create border window + local border_window = api.nvim_open_win(border_buffer, true, border_opts) + vim.cmd('set winhl=Normal:Floating') + + -- create a unlisted scratch buffer + if LAZYGIT_BUFFER == nil or vim.fn.bufwinnr(LAZYGIT_BUFFER) == -1 then + LAZYGIT_BUFFER = api.nvim_create_buf(false, true) + else + LAZYGIT_LOADED = true + end + -- create file window, enter the window, and use the options defined in opts + local _ = api.nvim_open_win(LAZYGIT_BUFFER, true, opts) + + vim.bo[LAZYGIT_BUFFER].filetype = 'lazygit' + + vim.cmd('setlocal bufhidden=hide') + vim.cmd('setlocal nocursorcolumn') + vim.cmd('set winblend=' .. vim.g.lazygit_floating_window_winblend) + + -- use autocommand to ensure that the border_buffer closes at the same time as the main buffer + local cmd = [[autocmd WinLeave silent! execute 'hide']] + vim.cmd(cmd) + cmd = [[autocmd WinLeave silent! execute 'silent bdelete! %s']] + vim.cmd(cmd:format(border_buffer)) +end + +return { + open_floating_window = open_floating_window, +} diff --git a/lua/telescope/_extensions/lazygit.lua b/lua/telescope/_extensions/lazygit.lua new file mode 100644 index 0000000..daccba3 --- /dev/null +++ b/lua/telescope/_extensions/lazygit.lua @@ -0,0 +1,86 @@ +local pickers = require("telescope.pickers") +local finders = require("telescope.finders") +local action_set = require("telescope.actions.set") +local action_state = require("telescope.actions.state") +local conf = require("telescope.config").values +local lazygit_utils = require("lazygit.utils") + + +local function open_lazygit(prompt_buf) + local entry = action_state.get_selected_entry() + vim.fn.execute('cd ' .. entry.value) + + local cmd = [[lua require"lazygit".lazygit(nil)]] + vim.api.nvim_command(cmd) + + vim.cmd('stopinsert') + vim.cmd([[execute "normal i"]]) + vim.fn.feedkeys('j') + vim.api.nvim_buf_set_keymap(0, 't', '', '', {noremap = true, silent = true}) +end + + +local lazygit_repos = function(opts) + local displayer = require("telescope.pickers.entry_display").create { + separator = "", + -- TODO: make use of telescope geometry + items = { + {width = 4}, + {width = 55}, + {remaining = true}, + }, + } + + local repos = {} + for _, v in pairs(lazygit_utils.lazygit_visited_git_repos) do + if v == nil then + goto skip + end + + local index = #repos + 1 + local entry = + { + idx = index, + value = v:gsub("%s", ""), + -- retrieve git repo name + repo_name= v:gsub("%s", ""):match("^.+/(.+)$"), + } + + table.insert(repos, index, entry) + + ::skip:: + end + + pickers.new(opts or {}, { + prompt_title = "lazygit repos", + finder = finders.new_table { + results = repos, + entry_maker = function(entry) + local make_display = function() + return displayer + { + {entry.idx}, + {entry.repo_name}, + } + end + + return { + value = entry.value, + ordinal = string.format("%s %s", entry.idx, entry.repo_name), + display = make_display, + } + end, + }, + sorter = conf.generic_sorter(opts), + attach_mappings = function(_, _) + action_set.select:replace(open_lazygit) + return true + end + }):find() +end + +return require("telescope").register_extension({ + exports = { + lazygit = lazygit_repos, + } +})