Compare commits
57 Commits
@ -0,0 +1,2 @@
|
|||||||
|
.aider*
|
||||||
|
debug.log
|
@ -0,0 +1,106 @@
|
|||||||
|
# Todoer.nvim - Enhanced TODO Management for Neovim
|
||||||
|
|
||||||
|
A Neovim plugin that provides enhanced TODO list management with smart handling of checkboxes, indentation, and list items in markdown and other text formats.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
✅ **Current Functionality:**
|
||||||
|
- Smart TODO item creation with `<leader>ti`, `<leader>ta`, `<leader>to`
|
||||||
|
- Toggle checkboxes with `<leader>tt`
|
||||||
|
- Remove TODO markup with `<leader>td`
|
||||||
|
- Smart Enter key handling in insert mode:
|
||||||
|
- Creates new TODO items with proper indentation
|
||||||
|
- Handles empty TODO items by outdenting or terminating lists
|
||||||
|
- Tab/Shift-Tab indentation for TODO items
|
||||||
|
- Supports multiple list markers: `-`, `*`, `+`
|
||||||
|
- Works with markdown, text, and norg files by default
|
||||||
|
|
||||||
|
## Known Issues
|
||||||
|
|
||||||
|
🐛 **Current Bugs:**
|
||||||
|
- Tab indentation handling still uses feedkeys and needs refactoring
|
||||||
|
- Shift-Tab behavior may not be consistent with spaces vs tabs
|
||||||
|
- Enter key (<CR>) handling has several issues:
|
||||||
|
- Doesn't properly handle empty lines after TODO items
|
||||||
|
- Cursor positioning can be incorrect in some cases
|
||||||
|
- Outdenting behavior is inconsistent
|
||||||
|
- Edge cases with mixed indentation (spaces + tabs) not fully handled
|
||||||
|
- Cursor positioning could be improved in several scenarios
|
||||||
|
- Debug output is currently only enabled for Tab/Shift-Tab but not other operations
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
|
||||||
|
📌 **Planned Improvements:**
|
||||||
|
- [ ] Refactor tab indentation to use proper buffer operations
|
||||||
|
- [ ] Fix Enter key handling for all cases
|
||||||
|
- [ ] Add consistent debug output for all operations
|
||||||
|
- [ ] Improve cursor positioning logic
|
||||||
|
- [ ] Handle empty lines and edge cases better
|
||||||
|
- [ ] Add support for numbered lists
|
||||||
|
- [ ] Add support for nested TODO items with proper indentation
|
||||||
|
- [ ] Add visual selection operations for multiple TODOs
|
||||||
|
- [ ] Add commands for archiving completed items
|
||||||
|
- [ ] Add support for custom TODO markers
|
||||||
|
- [ ] Add support for custom checkbox states (e.g., `[ ]`, `[x]`, `[-]`)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Using [lazy.nvim](https://github.com/folke/lazy.nvim):
|
||||||
|
|
||||||
|
```lua
|
||||||
|
{
|
||||||
|
'your-username/todoer.nvim',
|
||||||
|
config = function()
|
||||||
|
require('todoer').setup({
|
||||||
|
-- Optional: override default filetypes
|
||||||
|
filetypes = { 'markdown', 'text', 'norg' }
|
||||||
|
})
|
||||||
|
end
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Debugging Recommendations
|
||||||
|
|
||||||
|
To help diagnose issues, you can:
|
||||||
|
|
||||||
|
1. Add `debug_line()` calls to key functions like `press_enter()`
|
||||||
|
2. Create a debug mode that can be toggled via:
|
||||||
|
```lua
|
||||||
|
local debug_mode = false
|
||||||
|
```
|
||||||
|
3. Make debug output more structured with timestamps and operation types
|
||||||
|
4. Log cursor positions before/after operations
|
||||||
|
5. Add validation of line states after modifications
|
||||||
|
|
||||||
|
Example debug output format:
|
||||||
|
```
|
||||||
|
[DEBUG] [TIMESTAMP] [OPERATION]
|
||||||
|
Line: 42
|
||||||
|
Before: {indent=" ", marker="-", is_todo=true}
|
||||||
|
After: {indent=" ", marker="-", is_todo=true}
|
||||||
|
Cursor: {lnum=42, col=6} -> {lnum=43, col=4}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Default configuration:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
{
|
||||||
|
filetypes = { "markdown", "text", "norg" } -- Filetypes to activate the plugin for
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Keybindings
|
||||||
|
|
||||||
|
Normal Mode:
|
||||||
|
- `<leader>ti` - Convert line to TODO (cursor after `] `)
|
||||||
|
- `<leader>ta` - Convert line to TODO (cursor at end)
|
||||||
|
- `<leader>to` - Insert new TODO line below
|
||||||
|
- `<leader>tt` - Toggle checkbox state
|
||||||
|
- `<leader>td` - Remove TODO markup
|
||||||
|
|
||||||
|
Insert Mode:
|
||||||
|
- `<CR>` - Smart enter behavior for TODO items
|
||||||
|
- `<Tab>` - Indent TODO item
|
||||||
|
- `<S-Tab>` - Outdent TODO item
|
@ -0,0 +1,4 @@
|
|||||||
|
# TODO
|
||||||
|
|
||||||
|
- [ ] Fix press_enter when line is indented
|
||||||
|
- [ ] When line is a todo, tab indents
|
@ -0,0 +1,411 @@
|
|||||||
|
-- Create the module table
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
-- Default configuration
|
||||||
|
local defaults = {
|
||||||
|
filetypes = { "markdown", "text", "norg" }, -- Filetypes to activate the plugin for
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Helper: Parses line details for a given line number (1-based)
|
||||||
|
-- Returns table: { line = str, indent = str, marker = str|nil, content = str, is_todo = bool, prefix_len = number } or nil
|
||||||
|
local function get_line_details(lnum)
|
||||||
|
-- Use nvim_buf_get_lines which is 0-indexed for ranges
|
||||||
|
local lines = vim.api.nvim_buf_get_lines(0, lnum - 1, lnum, false)
|
||||||
|
if not lines or #lines == 0 then return nil end -- Handle potential errors getting line
|
||||||
|
local line = lines[1]
|
||||||
|
|
||||||
|
-- Define patterns
|
||||||
|
-- Pattern to capture the full TODO prefix, including trailing space(s)
|
||||||
|
-- Captures: 1: Full prefix, 2: Indent, 3: Marker, 4: Checkbox state
|
||||||
|
local todo_prefix_pattern = "^(%s*([%-%*%+])%s+%[([ x])%]%s*)"
|
||||||
|
-- Pattern to capture a regular list prefix
|
||||||
|
-- Captures: 1: Full prefix, 2: Indent, 3: Marker
|
||||||
|
local list_prefix_pattern = "^(%s*([%-%*%+])%s+)"
|
||||||
|
|
||||||
|
local indent = ""
|
||||||
|
local marker = nil
|
||||||
|
local content = ""
|
||||||
|
local is_todo = false
|
||||||
|
local prefix_len = 0
|
||||||
|
|
||||||
|
-- Try matching the TODO prefix first
|
||||||
|
local todo_prefix, todo_indent, todo_marker, _ = string.match(line, todo_prefix_pattern)
|
||||||
|
|
||||||
|
if todo_prefix then
|
||||||
|
-- It's a TODO line
|
||||||
|
is_todo = true
|
||||||
|
indent = todo_indent
|
||||||
|
marker = todo_marker
|
||||||
|
prefix_len = #todo_prefix
|
||||||
|
content = string.sub(line, prefix_len + 1) -- Content is everything after the matched prefix
|
||||||
|
else
|
||||||
|
-- Not a TODO line, try matching a regular list prefix
|
||||||
|
local list_prefix, list_indent, list_marker = string.match(line, list_prefix_pattern)
|
||||||
|
if list_prefix then
|
||||||
|
-- It's a regular list line
|
||||||
|
is_todo = false
|
||||||
|
indent = list_indent
|
||||||
|
marker = list_marker
|
||||||
|
prefix_len = #list_prefix
|
||||||
|
content = string.sub(line, prefix_len + 1) -- Content is everything after the matched prefix
|
||||||
|
else
|
||||||
|
-- Not a list line either, treat as plain text
|
||||||
|
is_todo = false
|
||||||
|
indent = string.match(line, "^%s*") or ""
|
||||||
|
marker = nil
|
||||||
|
prefix_len = #indent
|
||||||
|
content = string.sub(line, prefix_len + 1) -- Use sub, trim might be too aggressive
|
||||||
|
-- content = vim.trim(line) -- Old way
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
line = line,
|
||||||
|
indent = indent,
|
||||||
|
marker = marker, -- '-', '*', '+', or nil
|
||||||
|
content = content,
|
||||||
|
is_todo = is_todo,
|
||||||
|
prefix_len = prefix_len, -- Store the calculated prefix length
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Helper: Creates proper indent string based on buffer settings
|
||||||
|
local function get_indent_string()
|
||||||
|
local sw = vim.bo.shiftwidth
|
||||||
|
local et = vim.bo.expandtab
|
||||||
|
return et and string.rep(" ", sw) or "\t"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Debug helper to print line details
|
||||||
|
local function debug_line(msg, details)
|
||||||
|
print(string.format(
|
||||||
|
"[DEBUG] %s:\n indent: '%s' (len=%d)\n marker: '%s'\n content: '%s'\n is_todo: %s",
|
||||||
|
msg,
|
||||||
|
details.indent,
|
||||||
|
#details.indent,
|
||||||
|
details.marker or "nil",
|
||||||
|
details.content,
|
||||||
|
details.is_todo
|
||||||
|
))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Helper: Gets details for the current line including cursor position
|
||||||
|
local function get_current_line_details_with_cursor()
|
||||||
|
local cursor_pos = vim.api.nvim_win_get_cursor(0)
|
||||||
|
local lnum = cursor_pos[1] -- 1-based line number
|
||||||
|
local col = cursor_pos[2] -- 0-based byte column
|
||||||
|
return get_line_details(lnum), lnum, col
|
||||||
|
end
|
||||||
|
|
||||||
|
-- <leader>ti: Convert line to TODO, cursor after "] " (Normal mode)
|
||||||
|
local function add_todo_insert_mode()
|
||||||
|
local details, lnum = get_current_line_details_with_cursor() -- Use basic helper is fine
|
||||||
|
-- Don't convert if already a TODO or if getting details failed
|
||||||
|
if not details or details.is_todo then return end
|
||||||
|
|
||||||
|
local use_marker = details.marker or "-" -- Default to '-' if no list marker found
|
||||||
|
-- Use the raw content calculated by the improved get_line_details
|
||||||
|
local text_content = details.content
|
||||||
|
|
||||||
|
local new_line = details.indent .. use_marker .. " [ ] " .. text_content
|
||||||
|
-- Replace current line (lnum is 1-based, set_lines is 0-based)
|
||||||
|
vim.api.nvim_buf_set_lines(0, lnum - 1, lnum, false, { new_line })
|
||||||
|
|
||||||
|
-- Set cursor position: 1-based row, 0-based byte column
|
||||||
|
-- Position cursor right after the "] "
|
||||||
|
local cursor_col_bytes = #(details.indent .. use_marker .. " [ ] ") -- Use Lua # operator
|
||||||
|
vim.api.nvim_win_set_cursor(0, { lnum, cursor_col_bytes })
|
||||||
|
end
|
||||||
|
|
||||||
|
-- <leader>ta: Convert line to TODO, cursor at end of line (Normal mode)
|
||||||
|
local function add_todo_append_mode()
|
||||||
|
local details, lnum = get_current_line_details_with_cursor() -- Use basic helper is fine
|
||||||
|
if not details or details.is_todo then return end
|
||||||
|
|
||||||
|
local use_marker = details.marker or "-"
|
||||||
|
local text_content = details.content
|
||||||
|
|
||||||
|
local new_line = details.indent .. use_marker .. " [ ] " .. text_content
|
||||||
|
vim.api.nvim_buf_set_lines(0, lnum - 1, lnum, false, { new_line })
|
||||||
|
|
||||||
|
-- Set cursor position: 1-based row, 0-based byte column
|
||||||
|
-- Position cursor at the very end of the line content
|
||||||
|
local cursor_col_bytes = #new_line -- Use Lua # operator
|
||||||
|
vim.api.nvim_win_set_cursor(0, { lnum, cursor_col_bytes })
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- <leader>to: Insert new TODO line below, cursor after "] " (Normal mode)
|
||||||
|
local function add_todo_new_line_mode()
|
||||||
|
local details, lnum = get_current_line_details_with_cursor() -- Use basic helper is fine
|
||||||
|
if not details then return end -- Should not happen usually, but check anyway
|
||||||
|
|
||||||
|
-- Determine indent and marker based on current line, default to '-' if no marker
|
||||||
|
local new_indent = details.indent
|
||||||
|
local new_marker = details.marker or "-"
|
||||||
|
|
||||||
|
local new_line_content = new_indent .. new_marker .. " [ ] "
|
||||||
|
-- Insert after current line (lnum is 1-based, set_lines is 0-based for ranges)
|
||||||
|
vim.api.nvim_buf_set_lines(0, lnum, lnum, false, { new_line_content })
|
||||||
|
|
||||||
|
-- Set cursor position: 1-based row (lnum + 1), 0-based byte column
|
||||||
|
-- Position cursor at the end of the new line (after "] ")
|
||||||
|
local cursor_col_bytes = #new_line_content -- Use Lua # operator
|
||||||
|
vim.api.nvim_win_set_cursor(0, { lnum + 1, cursor_col_bytes })
|
||||||
|
end
|
||||||
|
|
||||||
|
-- <leader>td: Remove TODO markup from the current line (Normal mode)
|
||||||
|
local function remove_todo_markup()
|
||||||
|
local details, lnum, original_col = get_current_line_details_with_cursor()
|
||||||
|
if not details or not details.is_todo then return end -- Only act on TODO lines
|
||||||
|
|
||||||
|
-- Construct the new line without the TODO markup "[ ] " or "[x] "
|
||||||
|
-- Ensure marker exists, default to '-' if somehow nil (shouldn't happen if is_todo)
|
||||||
|
local use_marker = details.marker or "-"
|
||||||
|
local new_line = details.indent .. use_marker .. " " .. details.content
|
||||||
|
|
||||||
|
-- Calculate the position where the markup started
|
||||||
|
-- This is the length of the indent + marker + one space
|
||||||
|
local markup_start_col = #(details.indent .. use_marker .. " ")
|
||||||
|
|
||||||
|
-- The markup itself is 4 characters: '[', ' ' or 'x', ']', ' '
|
||||||
|
local markup_len = 4
|
||||||
|
|
||||||
|
-- Update the line content
|
||||||
|
vim.api.nvim_buf_set_lines(0, lnum - 1, lnum, false, { new_line })
|
||||||
|
|
||||||
|
-- Calculate the new cursor column
|
||||||
|
local new_col
|
||||||
|
if original_col < markup_start_col then
|
||||||
|
-- Cursor was before the markup, keep its position
|
||||||
|
new_col = original_col
|
||||||
|
elseif original_col >= markup_start_col and original_col < markup_start_col + markup_len then
|
||||||
|
-- Cursor was inside the markup, move it to the start of where the markup was
|
||||||
|
new_col = markup_start_col
|
||||||
|
else
|
||||||
|
-- Cursor was after the markup, shift it left by the length of the removed markup
|
||||||
|
new_col = original_col - markup_len
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Ensure the column is not negative (shouldn't happen here, but good practice)
|
||||||
|
new_col = math.max(0, new_col)
|
||||||
|
|
||||||
|
-- Set the new cursor position
|
||||||
|
vim.api.nvim_win_set_cursor(0, { lnum, new_col })
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Internal function to set up buffer-local keymaps
|
||||||
|
local function setup_buffer_keymaps()
|
||||||
|
-- Handles Enter key press in Insert mode
|
||||||
|
local function press_enter()
|
||||||
|
local details, lnum = get_current_line_details_with_cursor() -- Use basic helper is fine
|
||||||
|
if not details then
|
||||||
|
-- Getting details failed, do nothing and let Neovim handle Enter.
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check if the line is a TODO item and if it has content after the marker
|
||||||
|
-- Use string.match with %S to check for any non-whitespace character
|
||||||
|
local has_content = details.content and string.match(details.content, "%S")
|
||||||
|
|
||||||
|
if details.is_todo and has_content then
|
||||||
|
-- Case 1: Non-empty TODO line
|
||||||
|
-- Create a new TODO line below with same indent/marker
|
||||||
|
local new_marker = details.marker or "-" -- Should always have marker if is_todo, but safety check
|
||||||
|
local new_line_content = details.indent .. new_marker .. " [ ] "
|
||||||
|
|
||||||
|
-- Insert the new line below the current one
|
||||||
|
vim.api.nvim_buf_set_lines(0, lnum, lnum, false, { new_line_content })
|
||||||
|
|
||||||
|
-- Move cursor to the new line, after the "] "
|
||||||
|
local cursor_col_bytes = #new_line_content
|
||||||
|
vim.api.nvim_win_set_cursor(0, { lnum + 1, cursor_col_bytes })
|
||||||
|
|
||||||
|
-- Tell Neovim to ignore the original <CR> press
|
||||||
|
vim.api.nvim_input('<Ignore>')
|
||||||
|
return -- Action handled
|
||||||
|
|
||||||
|
elseif details.is_todo and not has_content then
|
||||||
|
-- Case 2: Empty TODO line (e.g., "- [ ]") - Implement Outdenting
|
||||||
|
local current_indent = details.indent
|
||||||
|
local current_indent_len = #current_indent
|
||||||
|
local sw = vim.bo.shiftwidth -- Get buffer's shiftwidth
|
||||||
|
local et = vim.bo.expandtab -- Get buffer's expandtab setting
|
||||||
|
|
||||||
|
if current_indent_len > 0 then
|
||||||
|
-- Line is indented, calculate new indentation
|
||||||
|
local new_indent = ""
|
||||||
|
if et then -- Using spaces
|
||||||
|
local target_indent_len = math.max(0, current_indent_len - sw)
|
||||||
|
new_indent = string.rep(" ", target_indent_len)
|
||||||
|
else -- Using tabs
|
||||||
|
-- Remove the first tab character found
|
||||||
|
new_indent = string.gsub(current_indent, "^\t", "", 1)
|
||||||
|
-- Safety check: if gsub didn't change anything (e.g., indent was spaces),
|
||||||
|
-- try removing spaces as a fallback, though this is less ideal with noexpandtab
|
||||||
|
if new_indent == current_indent and current_indent_len > 0 then
|
||||||
|
local target_indent_len = math.max(0, current_indent_len - sw)
|
||||||
|
new_indent = string.rep(" ", target_indent_len)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Replace the current line with the outdented version
|
||||||
|
local new_marker = details.marker or "-"
|
||||||
|
local new_line_content = new_indent .. new_marker .. " [ ] "
|
||||||
|
vim.api.nvim_buf_set_lines(0, lnum - 1, lnum, false, { new_line_content })
|
||||||
|
|
||||||
|
-- Move cursor after the "] " on the modified line
|
||||||
|
local cursor_col_bytes = #new_line_content
|
||||||
|
vim.api.nvim_win_set_cursor(0, { lnum, cursor_col_bytes })
|
||||||
|
|
||||||
|
-- Tell Neovim to ignore the original <CR> press
|
||||||
|
vim.api.nvim_input('<Ignore>')
|
||||||
|
return -- Action handled
|
||||||
|
|
||||||
|
else
|
||||||
|
-- Line is not indented, terminate the list
|
||||||
|
-- Replace the line with an empty string
|
||||||
|
vim.api.nvim_buf_set_lines(0, lnum - 1, lnum, false, { "" })
|
||||||
|
-- Now, manually insert a newline below using the API
|
||||||
|
vim.api.nvim_buf_set_lines(0, lnum, lnum, false, { "" })
|
||||||
|
-- Move cursor to the new empty line
|
||||||
|
vim.api.nvim_win_set_cursor(0, { lnum + 1, 0 })
|
||||||
|
|
||||||
|
-- Tell Neovim to ignore the original <CR> press
|
||||||
|
vim.api.nvim_input('<Ignore>')
|
||||||
|
return -- Action handled
|
||||||
|
end
|
||||||
|
|
||||||
|
else
|
||||||
|
-- Case 3: Not a TODO line
|
||||||
|
-- Do nothing. Neovim will automatically handle the Enter key press
|
||||||
|
-- because this function didn't modify the buffer or call nvim_input('<Ignore>').
|
||||||
|
return -- Explicitly return nothing to be clear
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Handle tab indentation for TODO lines
|
||||||
|
local function press_tab()
|
||||||
|
local lnum = vim.api.nvim_win_get_cursor(0)[1]
|
||||||
|
local details = get_line_details(lnum)
|
||||||
|
|
||||||
|
print("\n[DEBUG TAB] Line "..lnum.." before:")
|
||||||
|
debug_line("Original", details)
|
||||||
|
|
||||||
|
if not details or not details.is_todo then
|
||||||
|
print("[DEBUG TAB] Not a TODO line, using default Tab")
|
||||||
|
return vim.api.nvim_replace_termcodes("<Tab>", true, false, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
local indent_str = get_indent_string()
|
||||||
|
local new_line = details.indent .. indent_str ..
|
||||||
|
(details.marker or "-") .. " [ ] " .. details.content
|
||||||
|
|
||||||
|
print("[DEBUG TAB] New line will be:", new_line)
|
||||||
|
print("[DEBUG TAB] Indent string:", "'"..indent_str.."' (length="..#indent_str..")")
|
||||||
|
vim.api.nvim_set_current_line(new_line)
|
||||||
|
|
||||||
|
-- Calculate new cursor column (preserve relative position)
|
||||||
|
local cursor_col = vim.api.nvim_win_get_cursor(0)[2]
|
||||||
|
local new_col = cursor_col + #indent_str
|
||||||
|
vim.api.nvim_win_set_cursor(0, {vim.api.nvim_win_get_cursor(0)[1], new_col})
|
||||||
|
|
||||||
|
return "" -- Prevent default behavior
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Handle shift-tab outdentation for TODO lines
|
||||||
|
local function press_shift_tab()
|
||||||
|
local lnum = vim.api.nvim_win_get_cursor(0)[1]
|
||||||
|
local details = get_line_details(lnum)
|
||||||
|
|
||||||
|
print("\n[DEBUG SHIFT-TAB] Line "..lnum.." before:")
|
||||||
|
debug_line("Original", details)
|
||||||
|
|
||||||
|
if not details or not details.is_todo or #details.indent == 0 then
|
||||||
|
print("[DEBUG SHIFT-TAB] Not a TODO line or at min indent, using default S-Tab")
|
||||||
|
return vim.api.nvim_replace_termcodes("<S-Tab>", true, false, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
local sw = vim.bo.shiftwidth
|
||||||
|
local new_indent = details.indent:sub(1, -sw - 1)
|
||||||
|
local new_line = new_indent ..
|
||||||
|
(details.marker or "-") .. " [ ] " .. details.content
|
||||||
|
|
||||||
|
print("[DEBUG SHIFT-TAB] New indent:", "'"..new_indent.."' (length="..#new_indent..")")
|
||||||
|
print("[DEBUG SHIFT-TAB] New line will be:", new_line)
|
||||||
|
vim.api.nvim_set_current_line(new_line)
|
||||||
|
|
||||||
|
-- Calculate new cursor column (preserve relative position)
|
||||||
|
local cursor_col = vim.api.nvim_win_get_cursor(0)[2]
|
||||||
|
local new_col = math.max(0, cursor_col - sw)
|
||||||
|
vim.api.nvim_win_set_cursor(0, {vim.api.nvim_win_get_cursor(0)[1], new_col})
|
||||||
|
|
||||||
|
return "" -- Prevent default behavior
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Toggles the checkbox [ ] <-> [x] for lines starting with -, *, +, or #
|
||||||
|
local function toggle_todo()
|
||||||
|
local line = vim.api.nvim_get_current_line()
|
||||||
|
-- Pattern captures:
|
||||||
|
-- 1: Prefix (indentation, marker '-', '*', '+', or '#', and whitespace)
|
||||||
|
-- 2: State (' ' or 'x')
|
||||||
|
-- 3: Rest of the line (after ']')
|
||||||
|
-- Adjusted pattern slightly to ensure it captures # correctly too if needed
|
||||||
|
local prefix, state, rest = string.match(line, "^([%s]*[%-#%*%+]%s*)%[([ x])%](.*)")
|
||||||
|
|
||||||
|
if prefix then -- Check if the pattern matched (i.e., it's a toggleable line)
|
||||||
|
-- Determine the new state
|
||||||
|
local new_state = (state == ' ') and 'x' or ' '
|
||||||
|
-- Reconstruct the line with the new state
|
||||||
|
local new_line = prefix .. "[" .. new_state .. "]" .. rest
|
||||||
|
-- Update the current line
|
||||||
|
vim.api.nvim_set_current_line(new_line)
|
||||||
|
end
|
||||||
|
-- If the pattern didn't match, do nothing.
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Set up buffer-local keymaps
|
||||||
|
-- Use buffer = 0 to target the current buffer
|
||||||
|
vim.keymap.set("n", "<leader>t", function() end, { desc = "+TODOs", buffer = 0 }) -- Placeholder
|
||||||
|
-- Keep <CR> mapping without expr = true
|
||||||
|
vim.keymap.set("i", "<CR>", press_enter, { desc = "Todoer: Handle Enter", buffer = 0 })
|
||||||
|
-- Tab handling for TODO items
|
||||||
|
vim.keymap.set("i", "<TAB>", press_tab, { desc = "Todoer: Handle Tab", buffer = 0 })
|
||||||
|
vim.keymap.set("i", "<S-Tab>", press_shift_tab, { desc = "Todoer: Handle Shift-Tab", buffer = 0 })
|
||||||
|
vim.keymap.set("n", "<leader>tt", toggle_todo, { desc = "Todoer: Toggle TODO", buffer = 0 })
|
||||||
|
-- New mappings for adding TODOs
|
||||||
|
vim.keymap.set("n", "<leader>ti", add_todo_insert_mode, { desc = "Todoer: Add TODO (cursor after marker)", buffer = 0, silent = true })
|
||||||
|
vim.keymap.set("n", "<leader>ta", add_todo_append_mode, { desc = "Todoer: Add TODO (cursor at end)", buffer = 0, silent = true })
|
||||||
|
vim.keymap.set("n", "<leader>to", add_todo_new_line_mode, { desc = "Todoer: Add new TODO line below", buffer = 0, silent = true })
|
||||||
|
-- Mapping for removing TODO markup
|
||||||
|
vim.keymap.set("n", "<leader>td", remove_todo_markup, { desc = "Todoer: Remove TODO markup", buffer = 0, silent = true })
|
||||||
|
|
||||||
|
|
||||||
|
-- Optional: Notify that keymaps are set for this buffer
|
||||||
|
-- vim.notify("Todoer keymaps activated for this buffer", vim.log.levels.INFO)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Setup function: Called by the user in their config
|
||||||
|
function M.setup(opts)
|
||||||
|
-- Merge user options with defaults
|
||||||
|
local config = vim.tbl_deep_extend("force", {}, defaults, opts or {})
|
||||||
|
|
||||||
|
-- Create an autocommand group to ensure we can clear it later if needed
|
||||||
|
local group = vim.api.nvim_create_augroup("TodoerUserSetup", { clear = true })
|
||||||
|
|
||||||
|
-- Create the autocommand
|
||||||
|
vim.api.nvim_create_autocmd("FileType", {
|
||||||
|
group = group,
|
||||||
|
pattern = config.filetypes, -- Use filetypes from config
|
||||||
|
desc = "Setup Todoer keymaps for specific filetypes",
|
||||||
|
callback = function()
|
||||||
|
-- Call the function that sets up buffer-local keymaps
|
||||||
|
setup_buffer_keymaps()
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Return the module table
|
||||||
|
return M
|
@ -1,98 +1,8 @@
|
|||||||
vim.keymap.set("n", "<leader>t", function() end, { desc = "+Todos" })
|
-- This file is intentionally left blank or can contain comments.
|
||||||
|
-- The plugin is now activated by calling require('todoer').setup()
|
||||||
-- add new todo line when previous is already a todo
|
-- in the user's Neovim configuration (e.g., init.lua or via a plugin manager).
|
||||||
|
|
||||||
local function press_enter()
|
-- Example user configuration (e.g., in init.lua or using lazy.nvim):
|
||||||
local current_line = vim.api.nvim_get_current_line()
|
-- require('todoer').setup({
|
||||||
-- Check if the current line matches the pattern
|
-- filetypes = { 'markdown', 'txt' } -- Optional: override default filetypes
|
||||||
local pattern = "^%- %[[ x]%] .*$"
|
-- })
|
||||||
local match = string.match(current_line, pattern)
|
|
||||||
|
|
||||||
if match then
|
|
||||||
if string.len(current_line) >= 7 then
|
|
||||||
vim.api.nvim_feedkeys("\n- [ ] ", "n", true)
|
|
||||||
else
|
|
||||||
-- go to normal mode before removing the content of the line
|
|
||||||
vim.api.nvim_feedkeys(vim.api.nvim_eval('"\\<esc>"'), "n", true)
|
|
||||||
vim.api.nvim_feedkeys("_", "n", true)
|
|
||||||
vim.api.nvim_feedkeys("C", "n", true)
|
|
||||||
vim.api.nvim_feedkeys("\n", "n", true)
|
|
||||||
-- vim.api.nvim_set_current_line("")
|
|
||||||
end
|
|
||||||
else
|
|
||||||
vim.api.nvim_feedkeys("\n", "n", true)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
vim.keymap.set("i", "<CR>", press_enter, { desc = "On enter", noremap = true, expr = true })
|
|
||||||
|
|
||||||
-- function that checks if the current line starts with the string "- [ ]" or "- [x]" and toggles the x
|
|
||||||
|
|
||||||
local function toggle_todo()
|
|
||||||
-- create a variable called openpattern with a pattern that matches a string that starts with any amount of spaces or tabs followed by "- [ ]"
|
|
||||||
local openpattern = "^%s*%- %[[ x]%]"
|
|
||||||
|
|
||||||
-- create a variable called closepattern with a pattern that matches a string that starts with any amount of spaces or tabs followed by "- [x]"
|
|
||||||
local closedpattern = "^%s*%- %[[ x]%]"
|
|
||||||
|
|
||||||
-- create a variable called titleopenpattern with a pattern that matches a string that starts with "# [ ]"
|
|
||||||
local titleopenpattern = "^# [ ]"
|
|
||||||
|
|
||||||
-- create a variable called titleclosedpattern with a pattern that matches a string that starts with "# [x]"
|
|
||||||
local titleclosedpattern = "^# [x]"
|
|
||||||
|
|
||||||
local line = vim.api.nvim_get_current_line()
|
|
||||||
|
|
||||||
if string.match(line, openpattern) then
|
|
||||||
line = string.gsub(line, openpattern, "- [x]")
|
|
||||||
vim.api.nvim_set_current_line(line)
|
|
||||||
elseif string.match(line, closedpattern) then
|
|
||||||
line = string.gsub(line, closedpattern, "- [ ]")
|
|
||||||
vim.api.nvim_set_current_line(line)
|
|
||||||
elseif string.match(line, titleopenpattern) then
|
|
||||||
line = string.gsub(line, titleopenpattern, "# [x]")
|
|
||||||
vim.api.nvim_set_current_line(line)
|
|
||||||
elseif string.match(line, titleclosedpattern) then
|
|
||||||
line = string.gsub(line, titleclosedpattern, "# [ ]")
|
|
||||||
vim.api.nvim_set_current_line(line)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- if string.sub(line, 1, 5) == "- [ ]" then
|
|
||||||
-- line = string.sub(line, 6, -1)
|
|
||||||
-- line = "- [x]" .. line
|
|
||||||
-- vim.api.nvim_set_current_line(line)
|
|
||||||
-- elseif string.sub(line, 1, 5) == "- [x]" then
|
|
||||||
-- line = string.sub(line, 6, -1)
|
|
||||||
-- line = "- [ ]" .. line
|
|
||||||
-- vim.api.nvim_set_current_line(line)
|
|
||||||
-- elseif string.sub(line, 1, 5) == "# [ ]" then
|
|
||||||
-- line = string.sub(line, 6, -1)
|
|
||||||
-- line = "# [x]" .. line
|
|
||||||
-- vim.api.nvim_set_current_line(line)
|
|
||||||
-- elseif string.sub(line, 1, 5) == "# [x]" then
|
|
||||||
-- line = string.sub(line, 6, -1)
|
|
||||||
-- line = "# [ ]" .. line
|
|
||||||
-- vim.api.nvim_set_current_line(line)
|
|
||||||
-- end
|
|
||||||
end
|
|
||||||
vim.keymap.set("n", "<leader>tt", toggle_todo, { desc = "Toggle todo" })
|
|
||||||
|
|
||||||
-- function that checks if the current line doesn't start with the string "- [ ] " and, if it doesn't, adds the string at the beginning of the line
|
|
||||||
local function add_todo()
|
|
||||||
local line = vim.api.nvim_get_current_line()
|
|
||||||
if string.sub(line, 1, 5) ~= "- [ ] " then
|
|
||||||
line = "- [ ] " .. line
|
|
||||||
vim.api.nvim_set_current_line(line)
|
|
||||||
vim.api.nvim_feedkeys("A", "n", true)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
vim.keymap.set("n", "<leader>ta", add_todo, { desc = "Add todo" })
|
|
||||||
|
|
||||||
-- function that checks if the current line starts with the string "- [ ]" or "- [x]" and, if it does, removes that string from the line
|
|
||||||
local function remove_todo()
|
|
||||||
local line = vim.api.nvim_get_current_line()
|
|
||||||
if string.sub(line, 1, 6) == "- [ ] " or string.sub(line, 1, 6) == "- [x] " then
|
|
||||||
line = string.sub(line, 7, -1)
|
|
||||||
vim.api.nvim_set_current_line(line)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
vim.keymap.set("n", "<leader>td", remove_todo, { desc = "Remove todo" })
|
|
||||||
|
Loading…
Reference in New Issue