<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[[Seeking] Converting a script to a Neovim plugin]]></title><description><![CDATA[<h1><a class="anchor-offset" name="seeked-skill"></a>Seeked skill</h1>
<p dir="auto">If you know how to build a Neovim plugin and allow for custom user keybinding, we can be friend <img src="https://forum.unfinishedprojects.net/assets/plugins/nodebb-plugin-emoji/emoji/android/1f642.png?v=272e367859e" class="not-responsive emoji emoji-android emoji--slightly_smiling_face" style="height:23px;width:auto;vertical-align:middle" title=":)" alt="🙂" /></p>
<h1><a class="anchor-offset" name="the-project"></a>The project</h1>
<p dir="auto">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.</p>
<h1><a class="anchor-offset" name="the-features"></a>The features</h1>
<ul>
<li>
<p dir="auto"><strong>Create/edit/update a task:</strong> Type #TW some text and use the default keybinding &lt;leader&gt;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.<br />
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.<br />
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.</p>
</li>
<li>
<p dir="auto"><strong>Delete a task:</strong> Using the default &lt;leader&gt;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.</p>
</li>
<li>
<p dir="auto"><strong>Undo actions:</strong> Revert the last delete action with the default &lt;leader&gt;tu keybinding.</p>
</li>
<li>
<p dir="auto"><strong>Retrieve task info summary:</strong> With the default &lt;leader&gt;ti keybinding, can call a notification window that will show you a summary of the task info.</p>
</li>
<li>
<p dir="auto"><strong>Mark task as completed:</strong> Can be done using &lt;leader&gt;tc keybinding.</p>
</li>
</ul>
<h1><a class="anchor-offset" name="the-missing-steps"></a>The missing steps</h1>
<p dir="auto">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:</p>
<ol>
<li>Create a correct git structure to allow for pulling by Neovim plugin managers.</li>
<li>Allow for users to customize the keybindings.</li>
</ol>
<h1><a class="anchor-offset" name="the-current-script"></a>The current script</h1>
<pre><code class="language-lua">local M = {}
-- Default
M.keybindings.keybindings = {
	create_or_update_task = "&lt;leader&gt;ta",
	task_delete = "&lt;leader&gt;td",
	task_undo = "&lt;leader&gt;tu",
	task_info = "&lt;leader&gt;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 &gt; 0 then
				task_cmd = task_cmd .. " " .. table.concat(additional_tags, " ")
			end

			-- Add project if available
			if #project &gt; 0 then
				task_cmd = task_cmd .. " project:" .. project
			elseif project == " " then
				task_cmd = task_cmd .. " project:"
			end

			-- Add start date if available
			if #start &gt; 0 then
				task_cmd = task_cmd .. " start:" .. start
			end

			-- Add due date if available and tag is not note
			if #due &gt; 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
</code></pre>
]]></description><link>https://forum.unfinishedprojects.net/topic/34/seeking-converting-a-script-to-a-neovim-plugin</link><generator>RSS for Node</generator><lastBuildDate>Mon, 06 Apr 2026 05:12:00 GMT</lastBuildDate><atom:link href="https://forum.unfinishedprojects.net/topic/34.rss" rel="self" type="application/rss+xml"/><pubDate>Sun, 05 Apr 2026 13:23:25 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to [Seeking] Converting a script to a Neovim plugin on Sun, 05 Apr 2026 15:46:32 GMT]]></title><description><![CDATA[<p dir="auto">Hey! I don’t know Neovim, but I just wanted to wish you good luck! That sounds like a fun project.</p>
]]></description><link>https://forum.unfinishedprojects.net/post/109</link><guid isPermaLink="true">https://forum.unfinishedprojects.net/post/109</guid><dc:creator><![CDATA[pyromania.tiger]]></dc:creator><pubDate>Sun, 05 Apr 2026 15:46:32 GMT</pubDate></item></channel></rss>