Skip to content
  • Wiki
  • About Us
  • Rules
  • Categories
  • 0 Unread 0
  • Recent
  • Tags
  • Users
  • Groups
  • Into The Fediverse
  • Chats
Skins
  • Light
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Default (Spacelab)
  • No Skin
Collapse
Brand Logo

UnfinishedProjects

Lemmy/Piefed Mastodon Codeberg
  1. Home
  2. Skill Exchange
  3. [Seeking] Converting a script to a Neovim plugin

[Seeking] Converting a script to a Neovim plugin

Scheduled Pinned Locked Moved Skill Exchange
2 Posts 2 Posters 20 Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • www-gemW Offline
    www-gemW Offline
    www-gem
    wrote last edited by
    #1

    Seeked skill

    If you know how to build a Neovim plugin and allow for custom user keybinding, we can be friend 🙂

    The project

    Few months ago, I decided to build something that would allow one to stay in Neovim while interacting with Taskwarrior. I didn’t want to reinvent Taskwarrior so I went with a simple script limited to my needs. After sharing it online, some people showed interest in this tool. As a result, I’ve added few features, but the remaining issue is to convert this script as a real Neovim plugin.

    The features

    • Create/edit/update a task: Type #TW some text and use the default keybinding <leader>ta to create a task with some text as description, and add a task annotation in the form of “+line filepath” so you can easily access this task’s line from Taskwarrior.
      The script will recognize the #TW pattern and ask for a project name, start and due date, and tags for this task. By default, the due date is set to start+1h to fit my specific needs, but you can change that by editing line 90.
      All these fields are optional. The task will be added to Taskwarrior, and the task UUID will be appended to the line which will be commented.

    • Delete a task: Using the default <leader>td keybinding will delete the current line if it has a valid task UUID and remove the task in Taskwarrior. In the background, it will also add task’ annotations to all tasks below the current line.

    • Undo actions: Revert the last delete action with the default <leader>tu keybinding.

    • Retrieve task info summary: With the default <leader>ti keybinding, can call a notification window that will show you a summary of the task info.

    • Mark task as completed: Can be done using <leader>tc keybinding.

    The missing steps

    I’ve never built a real Neovim plugin. and this project was designed to be one more script to my collection. Hence, there are two issues remaining to be solved to convert this idea to a plugin:

    1. Create a correct git structure to allow for pulling by Neovim plugin managers.
    2. Allow for users to customize the keybindings.

    The current script

    local M = {}
    -- Default
    M.keybindings.keybindings = {
    	create_or_update_task = "<leader>ta",
    	task_delete = "<leader>td",
    	task_undo = "<leader>tu",
    	task_info = "<leader>ti",
    }
    
    -- Function to allow users to define their own keybindings
    function M.setup(custom_keybindings)
    	-- Merge custom keybindings with the default ones
    	if custom_keybindings then
    		for action, key in pairs(custom_keybindings) do
    			if M.keybindings[action] then
    				M.keybindings[action] = key
    			end
    		end
    	end
    
    	-- Rebind the keys based on the defined keybindings
    	vim.keymap.set("n", M.keybindings.create_or_update_task, function()
    		M.create_or_update_task()
    	end)
    
    	vim.keymap.set("n", M.keybindings.task_delete, function()
    		M.task_delete()
    	end)
    
    	vim.keymap.set("n", M.keybindings.task_undo, function()
    		M.task_undo()
    	end)
    
    	vim.keymap.set("n", M.keybindings.task_info, function()
    		M.task_info()
    	end)
    end
    
    -- Annotation update function
    function M.annotation_update(line_nb)
    	if line_nb == 0 then
    		line_nb = vim.fn.line(".")
    	end
    	local total_lines = vim.api.nvim_buf_line_count(0)
    
    	for line = line_nb, total_lines do
    		local current_line = vim.fn.getline(line)
    		local task_id = string.match(current_line, "UUID: ([%w-]+)")
    		local annot_line_cmd = string.format("task %s export | jq '.[].annotations.[-1].description'", task_id)
    		local annot = vim.fn.system(annot_line_cmd)
    		local annot_line = string.match(annot, "+(%d+)")
    		annot_line = tonumber(annot_line)
    
    		if annot ~= "" and annot_line ~= line then
    			local file_path = vim.fn.expand("%:p")
    			local annotation = string.format("+%s %s", line, vim.fn.shellescape(file_path))
    			local annotate_cmd = string.format('task %s annotate "%s"', task_id, annotation)
    			vim.fn.system(annotate_cmd)
    			vim.notify("Annotation(s) updated")
    		elseif task_id and annot == "" then
    			vim.notify("Can't find UUID on line " .. line)
    		end
    	end
    end
    
    -- Create or update task
    function M.create_or_update_task()
    	local current_line = vim.fn.getline(".")
    	local file_path = vim.fn.expand("%:p") -- Get full path of current file
    	local line_number = vim.fn.line(".") -- Get current line number
    
    	-- Ask for parameters
    	local task_tag = ""
    	local start = vim.fn.input("Start date (MMDDYYHH:MM): ")
    	local due = vim.fn.input("Due date (default: start+1h): ")
    	local project = vim.fn.input("Project name: ")
    	local has_note = false
    	local additional_tags_input = vim.fn.input("Tags (separated by spaces): ")
    	local additional_tags = {}
    
    	-- Keywords to look for
    	local keywords = { "#TW" }
    
    	for _, keyword in ipairs(keywords) do
    		local kw_start_index, kw_end_index = string.find(current_line, keyword)
    		-- Check line validity
    		if not kw_start_index then
    			vim.notify("No valid keyword found")
    		else
    			local id_keyword = ":: UUID:"
    			local task_id = string.match(current_line, "UUID: ([%w-]+)")
    			local id_start_index = string.find(current_line, id_keyword)
    			local task_cmd
    
    			if task_id then
    				local task_description = string.sub(current_line, kw_end_index + 2, id_start_index - 2)
    				task_cmd = string.format('task %s mod %s "%s"', task_id, task_tag, task_description)
    			else
    				local task_description = string.sub(current_line, kw_end_index + 1)
    				task_cmd = string.format('task add %s "%s"', task_tag, task_description)
    			end
    
    			-- Add additional tags if available
    			for tag in additional_tags_input:gmatch("%S+") do
    				table.insert(additional_tags, "+" .. tag)
    				if string.match(tag, "note") then
    					has_note = true
    				end
    			end
    
    			if #additional_tags > 0 then
    				task_cmd = task_cmd .. " " .. table.concat(additional_tags, " ")
    			end
    
    			-- Add project if available
    			if #project > 0 then
    				task_cmd = task_cmd .. " project:" .. project
    			elseif project == " " then
    				task_cmd = task_cmd .. " project:"
    			end
    
    			-- Add start date if available
    			if #start > 0 then
    				task_cmd = task_cmd .. " start:" .. start
    			end
    
    			-- Add due date if available and tag is not note
    			if #due > 0 and not has_note then
    				task_cmd = task_cmd .. " due:" .. due
    			elseif has_note then
    				task_cmd = task_cmd .. " due:"
    			elseif due == " " then
    				task_cmd = task_cmd .. " due:"
    			else
    				task_cmd = task_cmd .. " due:start+1h"
    			end
    
    			-- Execute the task add command
    			local output = vim.fn.system(task_cmd)
    
    			-- Task update notification
    			local task_id = string.match(current_line, "UUID: ([%w-]+)")
    			if task_id then
    				vim.notify("Task updated")
    			end
    
    			-- Add annotation to new task
    			local new_task_id = string.match(output, "Created task (%d+)%.")
    			if new_task_id then
    				local tasks_number_cmd = "task count status=pending"
    				local tasks_number = vim.fn.system(tasks_number_cmd)
    				tasks_number = tasks_number:gsub("%s+$", "")
    				local new_task_id_cmd = string.format("task %s export | jq '.[].uuid' | sed 's/\"//g'", tasks_number)
    				new_task_id = vim.fn.system(new_task_id_cmd)
    				new_task_id = new_task_id:gsub("%s+$", "")
    
    				-- Annotate task with filename and line number
    				local annotation = string.format("+%s %s", line_number, vim.fn.shellescape(file_path))
    				local annotate_cmd = string.format('task %s annotate "%s"', new_task_id, annotation)
    				vim.fn.system(annotate_cmd)
    				vim.notify("Task created")
    
    				-- Add UUID to line
    				local line_id = current_line .. " :: UUID: " .. new_task_id
    				vim.fn.setline(".", line_id)
    
    				-- Comment the line
    				vim.api.nvim_command("normal! gcc")
    			elseif not task_id then
    				vim.notify("Failed to extract task ID")
    			end
    
    			-- Update annotation on line change
    			M.annotation_update(0)
    		end
    	end
    end
    
    -- Task delete function
    function M.task_delete()
    	local current_line = vim.fn.getline(".")
    	local task_id = string.match(current_line, "UUID: ([%w-]+)")
    	local status_cmd = string.format("task %s export | jq '.[].status' | sed 's/\"//g'", task_id)
    
    	if task_id then
    		local delete_cmd = string.format("task rc.confirmation=off del %s", task_id)
    		vim.fn.system(delete_cmd)
    		vim.notify("Task " .. task_id .. " deleted")
    		vim.api.nvim_command("normal! dd")
    		M.annotation_update(0)
    	else
    		vim.notify("Can't find UUID task")
    	end
    end
    
    function M.task_complete()
    	local current_line = vim.fn.getline(".")
    	local task_id = string.match(current_line, "UUID: ([%w-]+)")
    
    	if task_id then
    		local complete_cmd = string.format("task %s mod status:completed", task_id)
    		vim.fn.system(complete_cmd)
    		vim.notify("Task " .. task_id .. " completed")
    		M.annotation_update(0)
    	else
    		vim.notify("Can't find UUID task")
    	end
    end
    
    -- Undo function
    function M.task_undo()
    	local undo_cmd = string.format("task rc.confirmation=off undo")
    	local undo_output = vim.fn.system(undo_cmd)
    	vim.cmd("undo")
    	vim.notify("Undo output: ", undo_output)
    end
    
    -- Task info function
    function M.task_info()
    	local current_line = vim.fn.getline(".")
    	local task_id = string.match(current_line, "UUID: ([%w-]+)")
    	local info_cmd = string.format("task %s info | head -n 12", task_id)
    	local info = vim.fn.system(info_cmd)
    	vim.notify(info)
    end
    
    return M
    

    There's nothing best to everyone. Find what's best for YOU.

    1 Reply Last reply
    1
    • P Offline
      P Offline
      pyromania.tiger
      wrote last edited by
      #2

      Hey! I don’t know Neovim, but I just wanted to wish you good luck! That sounds like a fun project.

      1 Reply Last reply
      1

      Hello! It looks like you're interested in this conversation, but you don't have an account yet.

      Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.

      With your input, this post could be even better 💗

      Register Login
      Reply
      • Reply as topic
      Log in to reply
      • Oldest to Newest
      • Newest to Oldest
      • Most Votes


      • Login

      • Don't have an account? Register

      • Login or register to search.
      • First post
        Last post
      0
      • Wiki
      • About Us
      • Rules
      • Categories
      • Unread 0
      • Recent
      • Tags
      • Users
      • Groups
      • Into The Fediverse
      • Chats