From c1f1ec5cc11e1825df5d421d2ab68311b11704c7 Mon Sep 17 00:00:00 2001 From: pseudometa <73286100+chrisgrieser@users.noreply.github.com> Date: Mon, 21 Nov 2022 13:54:41 +0100 Subject: [PATCH] initial commit --- LICENSE | 21 +++++++ README.md | 51 +++++++++++++++++ lua/ghengis.lua | 148 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 220 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 lua/ghengis.lua diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6ff4cd4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Christopher Grieser + +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/README.md b/README.md new file mode 100644 index 0000000..34641a9 --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +# nvim-ghengis +Convenience file operations for neovim written in lua. + + +- [How is this different from `vim.eunuch`?](#how-is-this-different-from-vimeunuch) +- [Installation and Setup](#installation-and-setup) +- [Available Commands](#available-commands) +- [Why that name?](#why-that-name) + + +## How is this different from `vim.eunuch`? +- Written 100% in lua. +- Uses up-to-date nvim features like `vim.ui.input` or `vim.notify`. This means you can get nicer input fields via plugins like [dressing.nvim](https://github.com/stevearc/dressing.nvim), and nice confirmation notices with plugins like [nvim-notify](https://github.com/rcarriga/nvim-notify), if they are installed and setup. +- Some minor improvements like automatically keeping the extensions when no extension is given. +- Except for `trashFile` and `chmodx` only vim commands or lua os-modules are used to keep shell requirements to a minimum. + +## Installation and Setup + +```lua +-- Packer +use "chrisgrieser/nvim-ghengis" +``` + +> __Note__ +> `ghengis` requires no `.setup()` function. Simply create keybindings for the commands you want to use. + +```lua +local ghengis = require("ghengis") +local keymap = vim.keymap.set +keymap("n", "yp", ghengis.copyFilepath) +keymap("n", "yn", ghengis.copyFilename) +keymap("n", "cx", ghengis.chmodx) +keymap("n", "rf", ghengis.renameFile) +keymap("n", "nf", ghengis.createNewFile) +keymap("n", "yf", ghengis.duplicateFile) +keymap("n", "df", ghengis.trashFile) -- requires macOS or Linux `mv` command +keymap("x", "x", ghengis.moveSelectionToNewFile) +``` + +## Available Commands +- `ghengis.copyFilepath`: Copy the absolute file path. When `clipboard='unnamed[plus]`, copies to the `+` register, otherwise to `"`. +- `ghengis.copyFilename`: Copy file name. When `clipboard='unnamed[plus]`, copies to the `+` register, otherwise to `"`. +- `ghengis.chmodx`: Run `chmod +x` on the current file. +- `ghengis.renameFile`: Rename the current file. If no extension is provided, will keep the current file extension. +- `ghengis.createNewFile`: Create a new file. If no extension is provided, will keep use the extension of the current file. +- `ghengis.duplicateFile`: Duplicate the current file. If no extension is provided, will keep the current file extension. +- `ghengis.trashFile`: Move the current file to `$HOME/.Trash`. (Requires macOS or Linux, since is using `mv`.) +- `ghengis.moveSelectionToNewFile`: Visual Mode Command. Prompts for a new file name and moves the current selection to that new file. + +## Why that name? +A tribute to [vim.eunuch](https://github.com/tpope/vim-eunuch) – as opposed to childless eunuchs, it is said that [Ghengis Khan has fathered thousands of children](https://allthatsinteresting.com/genghis-khan-children). diff --git a/lua/ghengis.lua b/lua/ghengis.lua new file mode 100644 index 0000000..1e0200e --- /dev/null +++ b/lua/ghengis.lua @@ -0,0 +1,148 @@ +local M = {} + +local error = vim.log.levels.ERROR +local expand = vim.fn.expand +local fn = vim.fn +local cmd = vim.cmd + +local function leaveVisualMode() + -- https://github.com/neovim/neovim/issues/17735#issuecomment-1068525617 + local escKey = vim.api.nvim_replace_termcodes("", false, true, true) + vim.api.nvim_feedkeys(escKey, "nx", false) +end + +-------------------------------------------------------------------------------- + +---Performing common file operation tasks +---@param op string rename|duplicate|new|newFromSel +local function fileOp(op) + local dir = expand("%:p:h") + local oldName = expand("%:t") + local oldExt = expand("%:e") + local prevReg + if op == "newFromSel" then + prevReg = fn.getreg("z") + leaveVisualMode() + cmd [['<,'>delete z]] + end + + local promptStr + if op == "duplicate" then promptStr = "Duplicate File as: " + elseif op == "rename" then promptStr = "Rename File to: " + elseif op == "new" or op == "newFromSel" then promptStr = "Name for New File: " + end + + vim.ui.input({prompt = promptStr}, function(newName) + local invalidName + if newName then + invalidName = newName:find("^%s*$") or newName:find("/") or newName:find(":") or newName:find("\\") + end + + if not (newName) or invalidName then -- cancel + if op == "newFromSel" then + cmd [[undo]] -- undo deletion + fn.setreg("z", prevReg) -- restore register content + end + if invalidName then vim.notify(" Invalid Filename.", error) end + return + end + if not (newName) or invalidName then -- cancel + if op == "newFromSel" then + cmd [[undo]] -- undo deletion + fn.setreg("z", prevReg) -- restore register content + end + if invalidName then + vim.notify(" Invalid Filename.", error) + end + return + end + + local extProvided = newName:find(".%.") -- non-leading dot to exclude dotfile-dots + local isDotfile = newName:match("^%.") + if not (extProvided) and not (isDotfile) then + newName = newName .. "." .. oldExt + end + local filepath = dir .. "/" .. newName + + if op == "duplicate" then + cmd("saveas " .. filepath) + cmd("edit " .. filepath) + vim.notify(" Duplicated '" .. oldName .. "' as '" .. newName .. "'.") + elseif op == "rename" then + os.rename(oldName, newName) + cmd("edit " .. filepath) + cmd("bdelete #") + vim.notify(" Renamed '" .. oldName .. "' to '" .. newName .. "'.") + elseif op == "new" then + cmd("edit " .. filepath) + cmd("write " .. filepath) + elseif op == "newFromSel" then + cmd("edit " .. filepath) + cmd("put z") + cmd("write " .. filepath) + fn.setreg("z", prevReg) -- restore register content + end + end) +end + +---Rename Current File +function M.renameFile() fileOp("rename") end + +---Duplicate Current File +function M.duplicateFile() fileOp("duplicate") end + +---Create New File +function M.createNewFile() fileOp("new") end + +---Move Selection to New File +function M.moveSelectionToNewFile() fileOp("newFromSel") end + +-------------------------------------------------------------------------------- + +---copying file information +---@param operation string filename|filepath +local function copyOp(operation) + local useSystemClipb = vim.opt.clipboard:get()[1]:find("unnamed") + local reg = '"' + if useSystemClipb then reg = "+" end + + local toCopy + if operation == "filename" then + toCopy = expand("%:t") + elseif operation == "filepath" then + toCopy = expand("%:p") + end + + fn.setreg(reg, toCopy) + vim.notify(" COPIED\n " .. toCopy) +end + +---Copy absolute path of current file +function M.copyFilepath() copyOp("filepath") end + +---Copy name of current file +function M.copyFilename() copyOp("filename") end + +-------------------------------------------------------------------------------- + +---Run `chmod +x` on the current file. Requires `chmod`. +function M.chmodx() + local currentFile = expand("%:p") + os.execute("chmod +x '" .. currentFile .. "'") + vim.notify(" Execution permission granted. ") +end + +---Trash the Current File. Requires `mv`. +---@param opts? table +function M.trashFile(opts) + if not (opts) then opts = {trashLocation = "$HOME/.Trash/"} end + + local currentFile = fn.expand("%:p") + local filename = fn.expand("%:t") + cmd [[update!]] + os.execute('mv -f "' .. currentFile .. '" "' .. opts.trashLocation .. '"') + cmd [[bdelete]] + vim.notify(" '" .. filename .. "' deleted. ") +end + +return M