diff --git a/.github/actions/commit-and-push/action.yml b/.github/actions/commit-and-push/action.yml new file mode 100644 index 0000000..3b031ed --- /dev/null +++ b/.github/actions/commit-and-push/action.yml @@ -0,0 +1,24 @@ +name: 'Commit and Push' +description: 'Commit and push changes to the repository' +inputs: + commit-message: + description: 'Commit message' + required: true + files: + description: 'Files to add' + required: true + github_token: + description: 'GitHub token' + required: true +runs: + using: 'composite' + steps: + - name: Commit and Push + shell: bash + run: | + git config --global user.name 'github-actions[bot]' + git config --global user.email 'github-actions[bot]@users.noreply.github.com' + git remote set-url origin https://x-access-token:${{ inputs.github_token }}@github.com/${{ github.repository }}.git + git add ${{ inputs.files }} + git diff --quiet && git diff --staged --quiet || (git commit -m "${{ inputs.commit-message }}"; git pull --rebase; git push) + diff --git a/.github/workflows/panvimdoc.yml b/.github/workflows/panvimdoc.yml new file mode 100644 index 0000000..74e5da3 --- /dev/null +++ b/.github/workflows/panvimdoc.yml @@ -0,0 +1,29 @@ +name: panvimdoc + +on: + push: + branches: + - '*' + +permissions: + contents: write + +jobs: + docs: + runs-on: ubuntu-latest + name: pandoc to vimdoc + steps: + - uses: actions/checkout@v2 + - uses: kdheepak/panvimdoc@main + with: + vimdoc: 'dirvish-do' + pandoc: 'README.md' + description: '*dirvish-do*' + + - name: Commit and push documentation + uses: ./.github/actions/commit-and-push + with: + commit-message: 'Update Vimdoc based on README' + files: 'doc/*' + github_token: ${{ secrets.GITHUB_TOKEN }} + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..967277e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Phạm Bình An + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/doc/dirvish-do.txt b/doc/dirvish-do.txt new file mode 100644 index 0000000..343d522 --- /dev/null +++ b/doc/dirvish-do.txt @@ -0,0 +1,157 @@ +*dirvish-dovish.txt* *dirvish-dovish* + +============================================================================== +Table of Contents *dirvish-dovish-table-of-contents* + +1. Introduction |dirvish-dovish-introduction| +2. Features |dirvish-dovish-features| +3. Installation & Requirements |dirvish-dovish-installation-&-requirements| +4. Configuration |dirvish-dovish-configuration| + - In Lua |dirvish-dovish-configuration-in-lua| + - In Vimscript |dirvish-dovish-configuration-in-vimscript| +5. Usage |dirvish-dovish-usage| + - Keymaps |dirvish-dovish-usage-keymaps| + - Tips |dirvish-dovish-usage-tips| +6. Credit |dirvish-dovish-credit| + +============================================================================== +1. Introduction *dirvish-dovish-introduction* + + + The file manipulation commands for vim-dirvish + that you’ve always wanted + +============================================================================== +2. Features *dirvish-dovish-features* + +- Supports most file operations: create, delete, rename, copy, move +- Cross-platform support thanks to luv +- Easy to memorize |dirvish-dovish-mappings| +- Integration with LSP for renaming files,… + + +============================================================================== +3. Installation & Requirements *dirvish-dovish-installation-&-requirements* + +You’ll need: - Nvim 0.8 or later - dirvish.vim + + +Then install with your favorite package manager: + +>lua + -- lazy.nvim + { + 'brianhuster/dirvish-do.nvim', + dependencies = {'justinmk/vim-dirvish'} + } +< + +>vim + " Vim-Plug + Plug 'justinmk/vim-dirvish' + Plug 'brianhuster/dirvish-do.nvim' +< + + +============================================================================== +4. Configuration *dirvish-dovish-configuration* + +You can configure the keymaps to your liking. Here’s an example: + + +IN LUA *dirvish-dovish-configuration-in-lua* + +>lua + require('dirvish-do').setup(){ + keymaps = { + make_file = 'mf', + make_dir = 'md', + copy = 'cp', + move = 'mv', + rename = 'r', + remove = '', + }, + }) +< + + +IN VIMSCRIPT *dirvish-dovish-configuration-in-vimscript* + +>vim + v:lua.require'dirvish-do'.setup(#{ + \ keymaps: { + \ make_file: 'mf', + \ make_dir: 'md', + \ copy: 'cp', + \ move: 'mv', + \ move: 'r', + \ remove: '', + \ }, + \ }) +< + +See |v:lua-call| for more information on calling Lua functions from legacy +Vimscript. + + +============================================================================== +5. Usage *dirvish-dovish-usage* + + +KEYMAPS *dirvish-dovish-usage-keymaps* + +Below are the default keymaps. You can change them in the +|dirvish-dovish-configuration| + + -------------------------------------------------------------------------------- + Function Default Mode Tip to remember + ----------------------------------- --------- -------- ------------------------- + Create file mf Normal mf for “make file” + + Create directory md Normal md for “make directory” + + Delete under cursor Normal Just delete key + + Delete items in visual selection Visual Just delete key + + Rename under cursor r Normal r for “rename” + + Copy file to current directory cp Normal cp for “copy” + + Move file to current directory mv Normal mv for “move” + -------------------------------------------------------------------------------- +For example, you can use `yy` to yank a file, then move to a new directory and +use `p` to paste the file there. Or to move a file, you use `yy` to yank the +file, move to a new directory and use `mv` to move the file there. + +You can also use `y` in `visual line` mode to select many files to copy or +move. (Note: `visual line` mode is recommended so that you can yank the full +file path) + + +TIPS *dirvish-dovish-usage-tips* + +- Run |dirvish-do| to see the help file generated from this README +- Use `:checkhealth dirvish-do` to check your keymaps and configuration + + +============================================================================== +6. Credit *dirvish-dovish-credit* + +This is a fork of vim-dirvish-dovish + by Rogin Farrer that has +been rewritten in Lua. It uses |luv| and |built-in| Vim commands and functions +instead of shell commands for better cross-platform support out of the box. + +Thanks to Anton Kavalkou + for the inspiration +and the idea to integrate with LSP + +Big shout out to Melandel for laying the +foundation + +for this plugin! + +Generated by panvimdoc + +vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/doc/tags b/doc/tags new file mode 100644 index 0000000..0ab073d --- /dev/null +++ b/doc/tags @@ -0,0 +1,9 @@ +dirvish-dovish dirvish-dovish.txt /*dirvish-dovish* +dirvish-dovish-configuration dirvish-dovish.txt /*dirvish-dovish-configuration* +dirvish-dovish-credit dirvish-dovish.txt /*dirvish-dovish-credit* +dirvish-dovish-installation-&-requirements dirvish-dovish.txt /*dirvish-dovish-installation-&-requirements* +dirvish-dovish-introduction dirvish-dovish.txt /*dirvish-dovish-introduction* +dirvish-dovish-mappings dirvish-dovish.txt /*dirvish-dovish-mappings* +dirvish-dovish-table-of-contents dirvish-dovish.txt /*dirvish-dovish-table-of-contents* +dirvish-dovish-todo dirvish-dovish.txt /*dirvish-dovish-todo* +dirvish-dovish.txt dirvish-dovish.txt /*dirvish-dovish.txt* diff --git a/ftplugin/dirvish.lua b/ftplugin/dirvish.lua new file mode 100644 index 0000000..2712507 --- /dev/null +++ b/ftplugin/dirvish.lua @@ -0,0 +1,16 @@ +if vim.fn.has('nvim-0.8') == 0 then + vim.notify('dirvish-do.nvim only supports Nvim 0.8 and later', vim.log.levels.ERROR) + return +end + +local map = vim.keymap.set +local dirvido = require('dirvish-do') +local keymaps = dirvido.config.keymaps + +map({ 'n' }, keymaps.copy, dirvido.copy, { buffer = true, silent = true }) +map({ 'n' }, keymaps.move, dirvido.move, { buffer = true, silent = true }) +map({ 'n' }, keymaps.rename, dirvido.rename, { buffer = true, silent = true }) +map({ 'n' }, keymaps.remove, dirvido.nremove, { buffer = true, silent = true }) +map({ 'x' }, keymaps.remove, dirvido.vremove, { buffer = true, silent = true }) +map({ 'n' }, keymaps.make_file, dirvido.mkfile, { buffer = true, silent = true }) +map({ 'n' }, keymaps.make_dir, dirvido.mkdir, { buffer = true, silent = true }) diff --git a/lua/dirvish-do/compat.lua b/lua/dirvish-do/compat.lua new file mode 100644 index 0000000..e9bd921 --- /dev/null +++ b/lua/dirvish-do/compat.lua @@ -0,0 +1,3 @@ +if not vim.lsp.get_clients then + vim.lsp.get_clients = vim.lsp.get_active_clients +end diff --git a/lua/dirvish-do/health.lua b/lua/dirvish-do/health.lua new file mode 100644 index 0000000..3ca572e --- /dev/null +++ b/lua/dirvish-do/health.lua @@ -0,0 +1,23 @@ +local health = vim.health +local M = {} + +function M.check() + health.start('Check requirements') + if vim.fn.has('nvim-0.8') == 0 then + health.error('dirvish-dovish.nvim only supports Nvim 0.8 and later') + return + end + health.ok('Your Neovim version is supported') + + if not pcall(vim.fn["dirvish#remove_icon_fn"], -1) then + health.warn('vim-dirvish not installed', + 'Get it at `https://github.com/justinmk/vim-dirvish`') + else + health.ok('vim-dirvish is installed') + end + + health.start('Check your config') + health.info(vim.inspect(require('dirvish-do').config)) +end + +return M diff --git a/lua/dirvish-do/init.lua b/lua/dirvish-do/init.lua new file mode 100644 index 0000000..ab7a0db --- /dev/null +++ b/lua/dirvish-do/init.lua @@ -0,0 +1,181 @@ +local fs = vim.fs +local fn = vim.fn +local uv = vim.uv or vim.loop +local utils = require('dirvish-do.operators') +local lsp = require('dirvish-do.lsp') +local api = vim.api +local Dirvish = vim.cmd.Dirvish + +local M = {} + +M.config = { + keymaps = { + make_file = 'mf', + make_dir = 'md', + copy = 'cp', + move = 'mv', + rename = 'r', + remove = '', + }, +} + +local sep = utils.sep + +local function moveCursorTo(target) + fn.search('\\V' .. fn.escape(target, '\\') .. '\\$') +end + +local function getVisualSelectedLines() + local line_start = api.nvim_buf_get_mark(0, "<")[1] + local line_end = api.nvim_buf_get_mark(0, ">")[1] + + if line_start > line_end then + line_start, line_end = line_end, line_start + end + + --- Nvim API indexing is zero-based, end-exclusive + local lines = api.nvim_buf_get_lines(0, line_start - 1, line_end, false) + + return lines +end + + +local function get_register() + local reg = vim.fn.getreg() + return vim.split(reg, '\n', { trimempty = true }) +end + +M.mkfile = function() + local filename = fn.input('Enter filename: ') + filename = vim.trim(filename) + if #filename == 0 then + return + end + lsp.willCreateFiles(filename) + vim.cmd.edit("%" .. filename) + vim.cmd.write() + Dirvish() + moveCursorTo(fs.joinpath(fn.expand("%"), filename)) + lsp.didCreateFiles(filename) +end + +M.mkdir = function() + local dirname = fn.input('Directory name : ') + dirname = vim.trim(dirname) + if #dirname == 0 then + return + end + lsp.willCreateFiles(dirname) + local dirpath = fs.joinpath(fn.expand("%"), dirname) + local success, errname, errmsg = uv.fs_mkdir(dirpath, 493) + if not success then + vim.print( + ("%s: %s"):format(errname, errmsg), + vim.log.levels.ERROR) + else + Dirvish() + moveCursorTo(dirname .. sep) + lsp.didCreateFiles(dirpath) + end +end + +function M.rename() + local target = vim.trim(fn.getline('.')) + local isDir = target:sub(-1) == sep + local filename = fs.basename(isDir and target:sub(1, -2) or target) + local newname = fn.input('Enter new name: ', filename) + if not newname or #newname == 0 or vim.trim(newname) == target then + return + end + local newpath = fs.joinpath(fn.expand('%'), newname) + local success, errname, errmsg = utils.mv(target, newpath) + if not success then + vim.print( + ("%s: %s"):format(errname, errmsg), + vim.log.levels.ERROR) + else + Dirvish() + if isDir then + moveCursorTo(newname .. sep) + else + moveCursorTo(newname) + end + end +end + +M.copy = function() + local targets = get_register() + if #targets == 0 then + return + end + local new_dir = fn.expand("%") + local newpath + for _, target in ipairs(targets) do + local isDir = target:sub(-1) == sep + if isDir then + newpath = fs.joinpath(new_dir, fs.basename(target:sub(1, -2))) + utils.copydir(target, newpath) + else + newpath = fs.joinpath(new_dir, fs.basename(target)) + utils.copyfile(target, newpath) + end + end + Dirvish() + moveCursorTo(newpath) +end + +M.move = function() + local targets = get_register() + if #targets == 0 then + return + end + local new_dir = fn.expand("%") + for _, target in ipairs(targets) do + local isDir = target:sub(-1) == sep + local newpath = fs.joinpath(new_dir, fs.basename(target)) + local success, errname, errmsg = utils.mv(target, newpath) + if not success then + vim.print(string.format("%s: %s", errname, errmsg), vim.log.levels.ERROR) + else + Dirvish() + if isDir then + moveCursorTo(fs.basename(newpath) .. sep) + else + moveCursorTo(fs.basename(newpath)) + end + end + end +end + +M.vremove = function() + local lines = getVisualSelectedLines() + if #lines == 0 then + return + end + local check = fn.confirm("Delete " .. vim.inspect(lines), "&Yes\n&No", 2) + if check ~= 1 then + print("Cancelled") + return + end + for _, line in ipairs(lines) do + utils.rm(line) + end + Dirvish() +end + +function M.nremove() + local line = vim.trim(fn.getline('.')) + local check = fn.confirm("Delete " .. line, "&Yes\n&No", 2) + if check ~= 1 then + print("Cancelled") + return + end + utils.rm(line) + Dirvish() +end + +function M.setup(opts) + M.config = vim.tbl_deep_extend("force", M.config, opts or {}) +end + +return M diff --git a/lua/dirvish-do/lsp.lua b/lua/dirvish-do/lsp.lua new file mode 100644 index 0000000..d385c0f --- /dev/null +++ b/lua/dirvish-do/lsp.lua @@ -0,0 +1,73 @@ +require('dirvish-do.compat') + +local M = {} + +local lsp = vim.lsp + +local function send(method, params) + local clients = lsp.get_clients() + if #clients == 0 then + return + end + + for _, client in ipairs(clients) do + if client.supports_method(method) then + pcall(client.request, method, params, function(err, result) + if result and result.changes then + lsp.util.apply_workspace_edit(result) + end + end) + end + end +end + +local function send_rename(method, old_path, new_path) + local old_uri = vim.uri_from_fname(old_path) + local new_uri = vim.uri_from_fname(new_path) + + local params = { + files = { + { + oldUri = old_uri, + newUri = new_uri, + }, + }, + } + send(method, params) +end + +local send_file = function(method, path) + local uri = vim.uri_from_fname(path) + local params = { + files = { + uri = uri, + }, + } + send(method, params) +end + +function M.willRenameFiles(old_path, new_path) + send_rename("workspace/willRenameFiles", old_path, new_path) +end + +function M.didRenameFiles(old_path, new_path) + send_rename("workspace/didRenameFiles", old_path, new_path) +end + +function M.willCreateFiles(path) + send_file("workspace/willCreateFiles", path) +end + +function M.didCreateFiles(path) + send_file("workspace/didCreateFiles", path) +end + +function M.willDeleteFiles(path) + send_file("workspace/willDeleteFiles", path) +end + +function M.didDeleteFiles(path) + send_file("workspace/didDeleteFiles", path) +end + +return M diff --git a/lua/dirvish-do/operators.lua b/lua/dirvish-do/operators.lua new file mode 100644 index 0000000..25bf918 --- /dev/null +++ b/lua/dirvish-do/operators.lua @@ -0,0 +1,67 @@ +local M = {} + +local fs = vim.fs +local fn = vim.fn +local uv = vim.uv or vim.loop +local lsp = require('dirvish-do.lsp') + +M.sep = fn.exists('+shellslash') and not vim.o.shellslash and '\\' or '/' + +function M.rm(path) + local isDir = path:sub(-1) == "/" + if isDir then + if fs.rm then + fs.rm(path, { recursive = true }) + else + fn.delete(path, 'rf') + end + else + if fs.rm then + fs.rm(path) + else + fn.delete(path) + end + end +end + +function M.copyfile(file, newpath) + local success, errname, errmsg = uv.fs_copyfile(file, newpath) + if not success then + vim.print(string.format("%s: %s", errname, errmsg), vim.log.levels.ERROR) + end +end + +-- Copy dir recursively +function M.copydir(dir, newpath) + local handle = uv.fs_scandir(dir) + if not handle then + return + end + local success, errname, errmsg = uv.fs_mkdir(newpath, 493) + if not success then + vim.print(string.format("%s: %s", errname, errmsg), vim.log.levels.ERROR) + return + end + + while true do + local name, type = uv.fs_scandir_next(handle) + if not name then + break + end + local filepath = fs.joinpath(dir, name) + if type == "directory" then + M.copydir(filepath, fs.joinpath(newpath, name)) + else + M.copyfile(filepath, fs.joinpath(newpath, name)) + end + end +end + +function M.mv(oldPath, newPath) + lsp.willRenameFiles(oldPath, newPath) + local success, errname, errmsg = uv.fs_rename(oldPath, newPath) + lsp.didRenameFiles(oldPath, newPath) + return success, errname, errmsg +end + +return M