diff --git a/README.md b/README.md
index 087cce8c..db8e9995 100644
--- a/README.md
+++ b/README.md
@@ -43,7 +43,7 @@ lua require('Comment').setup()
First you need to call the `setup()` method to create the default mappings.
-> NOTE: If you are facing **Keybindings are mapped but they are not working** issue then please try [this](https://github.com/numToStr/Comment.nvim/issues/115#issuecomment-1032290098)
+> **Note** - If you are facing **Keybindings are mapped but they are not working** issue then please try [this](https://github.com/numToStr/Comment.nvim/issues/115#issuecomment-1032290098)
- Lua
@@ -243,6 +243,8 @@ This plugin has native **treesitter** support for calculating `commentstring` wh
For advance use cases, use [nvim-ts-context-commentstring](https://github.com/JoosepAlviste/nvim-ts-context-commentstring). See [`pre_hook`](#pre-hook) section for the integration.
+> **Note** - This plugin does not depend on [nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter) however it is recommended in order to easily install tree-sitter parsers.
+
### 🎣 Hooks
@@ -355,6 +357,8 @@ vim.api.nvim_command('set commentstring=//%s')
> Run `:h commentstring` for more help
+
+
2. You can also use this plugin interface to store both line and block commentstring for the filetype. You can treat this as a more powerful version of the `commentstring`
```lua
@@ -394,7 +398,7 @@ Although, `Comment.nvim` supports neovim's `commentstring` but unfortunately it
- [`pre_hook`](#hooks) - If a string is returned from this method then it will be used for commenting.
-- [`ft_table`](#languages) - If the current filetype is found in the table, then the string there will be used.
+- [`ft.lua`](#ft-lua) - If the current filetype is found in the table, then the string there will be used.
- `commentstring` - Neovim's native commentstring for the filetype
@@ -420,10 +424,10 @@ The following object is provided as an argument to `pre_hook` and `post_hook` fu
---Range of the selection that needs to be commented
---@class CommentRange
----@field srow number Starting row
----@field scol number Starting column
----@field erow number Ending row
----@field ecol number Ending column
+---@field srow integer Starting row
+---@field scol integer Starting column
+---@field erow integer Ending row
+---@field ecol integer Ending column
```
`CommentType`, `CommentMode` and `CommentMotion` all of them are exported from the plugin's utils for reuse
diff --git a/lua/Comment/api.lua b/lua/Comment/api.lua
index 9b1aeefc..f04b8fbb 100644
--- a/lua/Comment/api.lua
+++ b/lua/Comment/api.lua
@@ -197,7 +197,7 @@ end
---@param opmode OpMode
---@param cfg? CommentConfig
function api.uncomment_current_blockwise_op(opmode, cfg)
- Op.opfunc(opmode, cfg, U.cmode.uncomment, U.ctype.block, U.cmotion.line)
+ Op.opfunc(opmode, cfg or Config:get(), U.cmode.uncomment, U.ctype.block, U.cmotion.line)
end
--==========================================
diff --git a/lua/Comment/config.lua b/lua/Comment/config.lua
index 718e9d28..13614494 100644
--- a/lua/Comment/config.lua
+++ b/lua/Comment/config.lua
@@ -44,8 +44,8 @@
---@private
---@class RootConfig
---@field config CommentConfig
----@field position number[] To be used to restore cursor position
----@field count number Helps with dot-repeat support for count prefix
+---@field position integer[] To be used to restore cursor position
+---@field count integer Helps with dot-repeat support for count prefix
local Config = {
state = {},
config = {
@@ -88,6 +88,7 @@ function Config:get()
return self.config
end
+---@export ft
return setmetatable(Config, {
__index = function(this, k)
return this.state[k]
diff --git a/lua/Comment/extra.lua b/lua/Comment/extra.lua
index 6a2fff23..e22c969c 100644
--- a/lua/Comment/extra.lua
+++ b/lua/Comment/extra.lua
@@ -5,8 +5,17 @@ local A = vim.api
local extra = {}
----@param count number Line index
----@param ctype CommentType
+-- FIXME This prints `a` in i_CTRL-o
+---Moves the cursor and enters INSERT mode
+---@param row integer Starting row
+---@param col integer Ending column
+local function move_n_insert(row, col)
+ A.nvim_win_set_cursor(0, { row, col })
+ A.nvim_feedkeys('a', 'ni', true)
+end
+
+---@param count integer Line index
+---@param ctype integer
---@param cfg CommentConfig
local function ins_on_line(count, ctype, cfg)
local row, col = unpack(A.nvim_win_get_cursor(0))
@@ -18,40 +27,39 @@ local function ins_on_line(count, ctype, cfg)
ctype = ctype,
range = { srow = row, scol = col, erow = row, ecol = col },
}
- local lcs, rcs = U.parse_cstr(cfg, ctx)
+ local srow = row + count
+ local lcs, rcs = U.parse_cstr(cfg, ctx)
local line = A.nvim_get_current_line()
- local indent = U.grab_indent(line)
- local padding = U.get_padding(cfg.padding)
+ local indent = U.indent_len(line)
+ local padding = U.get_pad(cfg.padding)
-- We need RHS of cstr, if we are doing block comments or if RHS exists
-- because even in line comment RHS do exists for some filetypes like jsx_element, ocaml
- local if_rcs = (ctype == U.ctype.block or rcs) and padding .. rcs or ''
+ local if_rcs = U.is_empty(rcs) and rcs or padding .. rcs
- local srow = row + count
- local ll = indent .. lcs .. padding
- A.nvim_buf_set_lines(0, srow, srow, false, { ll .. if_rcs })
- local erow, ecol = srow + 1, #ll - 1
- U.move_n_insert(erow, ecol)
+ A.nvim_buf_set_lines(0, srow, srow, false, { table.concat({ string.rep(' ', indent), lcs, padding, if_rcs }) })
+
+ move_n_insert(srow + 1, indent + #lcs + #padding - 1)
U.is_fn(cfg.post_hook, ctx)
end
---Add a comment below the current line and goes to INSERT mode
----@param ctype CommentType
+---@param ctype integer See |comment.utils.ctype|
---@param cfg CommentConfig
function extra.insert_below(ctype, cfg)
ins_on_line(0, ctype, cfg)
end
---Add a comment above the current line and goes to INSERT mode
----@param ctype CommentType
+---@param ctype integer See |comment.utils.ctype|
---@param cfg CommentConfig
function extra.insert_above(ctype, cfg)
ins_on_line(-1, ctype, cfg)
end
---Add a comment at the end of current line and goes to INSERT mode
----@param ctype CommentType
+---@param ctype integer See |comment.utils.ctype|
---@param cfg CommentConfig
function extra.insert_eol(ctype, cfg)
local srow, scol = unpack(A.nvim_win_get_cursor(0))
@@ -66,16 +74,16 @@ function extra.insert_eol(ctype, cfg)
local lcs, rcs = U.parse_cstr(cfg, ctx)
local line = A.nvim_get_current_line()
- local padding = U.get_padding(cfg.padding)
+ local padding = U.get_pad(cfg.padding)
-- We need RHS of cstr, if we are doing block comments or if RHS exists
-- because even in line comment RHS do exists for some filetypes like jsx_element, ocaml
- local if_rcs = rcs and padding .. rcs or ''
+ local if_rcs = U.is_empty(rcs) and rcs or padding .. rcs
local ecol
if U.is_empty(line) then
-- If line is empty, start comment at the correct indentation level
- A.nvim_buf_set_lines(0, srow - 1, srow, false, { lcs .. padding .. if_rcs })
+ A.nvim_set_current_line(lcs .. padding .. if_rcs)
A.nvim_command('normal! ==')
ecol = #A.nvim_get_current_line() - #if_rcs - 1
else
@@ -84,12 +92,11 @@ function extra.insert_eol(ctype, cfg)
-- 2. Other than that, I am assuming that the users wants a space b/w the end of line and start of the comment
local space = vim.bo.filetype == 'python' and ' ' or ' '
local ll = line .. space .. lcs .. padding
-
+ A.nvim_set_current_line(ll .. if_rcs)
ecol = #ll - 1
- A.nvim_buf_set_lines(0, srow - 1, srow, false, { ll .. if_rcs })
end
- U.move_n_insert(srow, ecol)
+ move_n_insert(srow, ecol)
U.is_fn(cfg.post_hook, ctx)
end
diff --git a/lua/Comment/ft.lua b/lua/Comment/ft.lua
index c5bb5e87..09b40ef2 100644
--- a/lua/Comment/ft.lua
+++ b/lua/Comment/ft.lua
@@ -19,7 +19,8 @@ local M = {
}
---Lang table that contains commentstring (linewise/blockwise) for mutliple filetypes
----@type table { filetype = { linewise, blockwise } }
+---Structure = { filetype = { linewise, blockwise } }
+---@type table
local L = {
arduino = { M.cxx_l, M.cxx_b },
bash = { M.hash },
@@ -115,7 +116,7 @@ end
---Get a commentstring from the filtype list
---@param lang CommentLang
----@param ctype CommentType
+---@param ctype integer See |comment.utils.ctype|
---@return string
function ft.get(lang, ctype)
local l = ft.lang(lang)
@@ -124,16 +125,16 @@ end
---Get the commentstring(s) from the filtype list
---@param lang CommentLang
----@return string
+---@return string[]
function ft.lang(lang)
return L[lang]
end
---Get the tree in range by walking the whole tree recursively
----NOTE: This ignores `comment` parser as this is useless
+---NOTE: This ignores `comment` parser as this is not needed
---@param tree userdata Tree to be walked
----@param range number[] Range to check for
----@return userdata
+---@param range integer[] Range to check - {start_line, s_col, end_line, end_col}
+---@return userdata _ Returns a 'treesitter-languagetree'
function ft.contains(tree, range)
for lang, child in pairs(tree:children()) do
if lang ~= 'comment' and child:contains(range) then
@@ -166,6 +167,7 @@ function ft.calculate(ctx)
return ft.get(lang, ctx.ctype) or default
end
+---@export ft
return setmetatable(ft, {
__newindex = function(this, k, v)
this.set(k, v)
diff --git a/lua/Comment/opfunc.lua b/lua/Comment/opfunc.lua
index 099ed3ca..3a1302d3 100644
--- a/lua/Comment/opfunc.lua
+++ b/lua/Comment/opfunc.lua
@@ -6,18 +6,24 @@ local A = vim.api
local Op = {}
----@alias OpMode 'line'|'char'|'v'|'V' Vim operator-mode motions. Read `:h map-operator`
+---Vim operator-mode motions.
+---Read `:h :map-operator`
+---@alias OpMode
+---| 'line' # Vertical motion
+---| 'char' # Horizontal motion
+---| 'v' # Visual Block motion
+---| 'V' # Visual Line motion
---@class CommentCtx Comment context
----@field ctype CommentType
----@field cmode CommentMode
----@field cmotion CommentMotion
+---@field ctype integer See |comment.utils.ctype|
+---@field cmode integer See |comment.utils.cmode|
+---@field cmotion integer See |comment.utils.cmotion|
---@field range CommentRange
---@class OpFnParams Operator-mode function parameters
---@field cfg CommentConfig
----@field cmode CommentMode
----@field lines table List of lines
+---@field cmode integer See |comment.utils.cmode|
+---@field lines string[] List of lines
---@field rcs string RHS of commentstring
---@field lcs string LHS of commentstring
---@field range CommentRange
@@ -26,34 +32,23 @@ local Op = {}
---This function contains the core logic for comment/uncomment
---@param opmode OpMode
---@param cfg CommentConfig
----@param cmode CommentMode
----@param ctype CommentType
----@param cmotion CommentMotion
+---@param cmode integer See |comment.utils.cmode|
+---@param ctype integer See |comment.utils.ctype|
+---@param cmotion integer See |comment.utils.cmotion|
function Op.opfunc(opmode, cfg, cmode, ctype, cmotion)
- -- comment/uncomment logic
- --
- -- 1. type == line
- -- * decide whether to comment or not, if all the lines are commented then uncomment otherwise comment
- -- * also, store the minimum indent from all the lines (exclude empty line)
- -- * if comment the line, use cstr LHS and also considering the min indent
- -- * if uncomment the line, remove cstr LHS from lines
- -- * update the lines
- -- 2. type == block
- -- * check if the first and last is commented or not with cstr LHS and RHS respectively.
- -- * if both lines commented
- -- - remove cstr LHS from the first line
- -- - remove cstr RHS to end of the last line
- -- * if both lines uncommented
- -- - add cstr LHS after the leading whitespace and before the first char of the first line
- -- - add cstr RHS to end of the last line
- -- * update the lines
-
cmotion = cmotion == U.cmotion._ and U.cmotion[opmode] or cmotion
local range = U.get_region(opmode)
- local same_line = range.srow == range.erow
- local partial_block = cmotion == U.cmotion.char or cmotion == U.cmotion.v
- local block_x = partial_block and same_line
+ local partial = cmotion == U.cmotion.char or cmotion == U.cmotion.v
+ local block_x = partial and range.srow == range.erow
+
+ local lines = U.get_lines(range)
+
+ -- sometimes there might be a case when there are no lines
+ -- like, executing a text object returns nothing
+ if U.is_empty(lines) then
+ return
+ end
---@type CommentCtx
local ctx = {
@@ -64,22 +59,19 @@ function Op.opfunc(opmode, cfg, cmode, ctype, cmotion)
}
local lcs, rcs = U.parse_cstr(cfg, ctx)
- local lines = U.get_lines(range)
---@type OpFnParams
local params = {
cfg = cfg,
- cmode = cmode,
lines = lines,
lcs = lcs,
rcs = rcs,
+ cmode = cmode,
range = range,
}
- if block_x then
- ctx.cmode = Op.blockwise_x(params)
- elseif ctype == U.ctype.block and not same_line then
- ctx.cmode = Op.blockwise(params, partial_block)
+ if block_x or ctype == U.ctype.block then
+ ctx.cmode = Op.blockwise(params, partial)
else
ctx.cmode = Op.linewise(params)
end
@@ -99,152 +91,106 @@ end
---Line commenting
---@param param OpFnParams
----@return integer CMode
+---@return integer _ Returns a calculated comment mode
function Op.linewise(param)
- local lcs_esc, rcs_esc = U.escape(param.lcs), U.escape(param.rcs)
local pattern = U.is_fn(param.cfg.ignore)
- local padding, pp = U.get_padding(param.cfg.padding)
- local is_commented = U.is_commented(lcs_esc, rcs_esc, pp)
+ local padding = U.is_fn(param.cfg.padding)
+ local check = U.is_commented(param.lcs, param.rcs, padding)
-- While commenting a region, there could be lines being both commented and non-commented
-- So, if any line is uncommented then we should comment the whole block or vise-versa
local cmode = U.cmode.uncomment
- -- When commenting multiple line, it is to be expected that indentation should be preserved
- -- So, When looping over multiple lines we need to store the indentation of the mininum length (except empty line)
- -- Which will be used to semantically comment rest of the lines
- local min_indent = nil
+ ---When commenting multiple line, it is to be expected that indentation should be preserved
+ ---So, When looping over multiple lines we need to store the indentation of the mininum length (except empty line)
+ ---Which will be used to semantically comment rest of the lines
+ ---@type integer
+ local min_indent = -1
- -- If the given comde is uncomment then we actually don't want to compute the cmode or min_indent
+ -- If the given cmode is uncomment then we actually don't want to compute the cmode or min_indent
if param.cmode ~= U.cmode.uncomment then
for _, line in ipairs(param.lines) do
-- I wish lua had `continue` statement [sad noises]
if not U.ignore(line, pattern) then
- if cmode == U.cmode.uncomment and param.cmode == U.cmode.toggle then
- local is_cmt = is_commented(line)
- if not is_cmt then
- cmode = U.cmode.comment
- end
+ if cmode == U.cmode.uncomment and param.cmode == U.cmode.toggle and (not check(line)) then
+ cmode = U.cmode.comment
end
-- If local `cmode` == comment or the given cmode ~= uncomment, then only calculate min_indent
-- As calculating min_indent only makes sense when we actually want to comment the lines
if not U.is_empty(line) and (cmode == U.cmode.comment or param.cmode == U.cmode.comment) then
- local indent = U.grab_indent(line)
- if not min_indent or #min_indent > #indent then
- min_indent = indent
+ local len = U.indent_len(line)
+ if min_indent == -1 or min_indent > len then
+ min_indent = len
end
end
end
end
end
- -- If the comment mode given is not toggle than force that mode
- if param.cmode ~= U.cmode.toggle then
- cmode = param.cmode
- end
-
- local uncomment = cmode == U.cmode.uncomment
- for i, line in ipairs(param.lines) do
- if not U.ignore(line, pattern) then
- if uncomment then
- param.lines[i] = U.uncomment_str(line, lcs_esc, rcs_esc, pp)
- else
- param.lines[i] = U.comment_str(line, param.lcs, param.rcs, padding, min_indent)
+ if cmode == U.cmode.uncomment then
+ local uncomment = U.uncommenter(param.lcs, param.rcs, padding)
+ for i, line in ipairs(param.lines) do
+ if not U.ignore(line, pattern) then
+ param.lines[i] = uncomment(line)
+ end
+ end
+ else
+ local comment = U.commenter(param.lcs, param.rcs, padding, min_indent)
+ for i, line in ipairs(param.lines) do
+ if not U.ignore(line, pattern) then
+ param.lines[i] = comment(line)
end
end
end
+
A.nvim_buf_set_lines(0, param.range.srow - 1, param.range.erow, false, param.lines)
return cmode
end
----Full/Partial Block commenting
+---Full/Partial/Current-Line Block commenting
---@param param OpFnParams
---@param partial? boolean Comment the partial region (visual mode)
----@return integer CMode
+---@return integer _ Returns a calculated comment mode
function Op.blockwise(param, partial)
- -- Block wise, only when there are more than 1 lines
- local sln, eln = param.lines[1], param.lines[#param.lines]
- local lcs_esc, rcs_esc = U.escape(param.lcs), U.escape(param.rcs)
- local padding, pp = U.get_padding(param.cfg.padding)
-
- -- These string should be checked for comment/uncomment
- local sln_check, eln_check
- if partial then
- sln_check = sln:sub(param.range.scol + 1)
- eln_check = eln:sub(0, param.range.ecol + 1)
- else
- sln_check, eln_check = sln, eln
- end
-
- -- If given mode is toggle then determine whether to comment or not
- local cmode
- if param.cmode == U.cmode.toggle then
- local s_cmt = U.is_commented(lcs_esc, nil, pp)(sln_check)
- local e_cmt = U.is_commented(nil, rcs_esc, pp)(eln_check)
- cmode = (s_cmt and e_cmt) and U.cmode.uncomment or U.cmode.comment
- else
- cmode = param.cmode
- end
+ local is_x = #param.lines == 1 -- current-line blockwise
+ local lines = is_x and param.lines[1] or param.lines
- local l1, l2
+ local padding = U.is_fn(param.cfg.padding)
- if cmode == U.cmode.uncomment then
- l1 = U.uncomment_str(sln_check, lcs_esc, nil, pp)
- l2 = U.uncomment_str(eln_check, nil, rcs_esc, pp)
- else
- l1 = U.comment_str(sln_check, param.lcs, nil, padding)
- l2 = U.comment_str(eln_check, nil, param.rcs, padding)
+ local scol, ecol = nil, nil
+ if is_x or partial then
+ scol, ecol = param.range.scol, param.range.ecol
end
- if partial then
- l1 = sln:sub(0, param.range.scol) .. l1
- l2 = l2 .. eln:sub(param.range.ecol + 2)
+ -- If given mode is toggle then determine whether to comment or not
+ local cmode = param.cmode
+ if cmode == U.cmode.toggle then
+ local is_cmt = U.is_commented(param.lcs, param.rcs, padding, scol, ecol)(lines)
+ cmode = is_cmt and U.cmode.uncomment or U.cmode.comment
end
- A.nvim_buf_set_lines(0, param.range.srow - 1, param.range.srow, false, { l1 })
- A.nvim_buf_set_lines(0, param.range.erow - 1, param.range.erow, false, { l2 })
-
- return cmode
-end
-
----Block (left-right motion) commenting
----@param param OpFnParams
----@return integer CMode
-function Op.blockwise_x(param)
- local line = param.lines[1]
- local first = line:sub(0, param.range.scol)
- local mid = line:sub(param.range.scol + 1, param.range.ecol + 1)
- local last = line:sub(param.range.ecol + 2)
-
- local padding, pp = U.get_padding(param.cfg.padding)
-
- local yes, _, stripped = U.is_commented(U.escape(param.lcs), U.escape(param.rcs), pp)(mid)
-
- local cmode
- if param.cmode == U.cmode.toggle then
- cmode = yes and U.cmode.uncomment or U.cmode.comment
+ if cmode == U.cmode.uncomment then
+ lines = U.uncommenter(param.lcs, param.rcs, padding, scol, ecol)(lines)
else
- cmode = param.cmode
+ lines = U.commenter(param.lcs, param.rcs, padding, scol, ecol)(lines)
end
- if cmode == U.cmode.uncomment then
- A.nvim_set_current_line(first .. (stripped or mid) .. last)
+ if is_x then
+ A.nvim_set_current_line(lines)
else
- local lcs = param.lcs and param.lcs .. padding or ''
- local rcs = param.rcs and padding .. param.rcs or ''
- A.nvim_set_current_line(first .. lcs .. mid .. rcs .. last)
+ A.nvim_buf_set_lines(0, param.range.srow - 1, param.range.erow, false, lines)
end
return cmode
end
----Toggle line comment with count i.e vim.v.count
----Example: `10gl` will comment 10 lines
+---Line commenting with count i.e vim.v.count
+---Example: '10gl' will comment 10 lines
---@param count integer Number of lines
---@param cfg CommentConfig
----@param ctype CommentType
+---@param ctype integer See |comment.utils.ctype|
function Op.count(count, cfg, ctype)
local lines, range = U.get_count_lines(count)
diff --git a/lua/Comment/utils.lua b/lua/Comment/utils.lua
index 73344589..39d57747 100644
--- a/lua/Comment/utils.lua
+++ b/lua/Comment/utils.lua
@@ -7,15 +7,15 @@ local U = {}
---@alias CommentLines string[] List of lines inside the start and end index
---@class CommentRange Range of the selection that needs to be commented
----@field srow number Starting row
----@field scol number Starting column
----@field erow number Ending row
----@field ecol number Ending column
+---@field srow integer Starting row
+---@field scol integer Starting column
+---@field erow integer Ending row
+---@field ecol integer Ending column
---@class CommentMode Comment modes - Can be manual or computed via operator-mode
----@field toggle number Toggle action
----@field comment number Comment action
----@field uncomment number Uncomment action
+---@field toggle integer Toggle action
+---@field comment integer Comment action
+---@field uncomment integer Uncomment action
---An object containing comment modes
---@type CommentMode
@@ -26,8 +26,8 @@ U.cmode = {
}
---@class CommentType Comment types
----@field line number Use linewise commentstring
----@field block number Use blockwise commentstring
+---@field line integer Use linewise commentstring
+---@field block integer Use blockwise commentstring
---An object containing comment types
---@type CommentType
@@ -37,12 +37,12 @@ U.ctype = {
}
---@class CommentMotion Comment motion types
----@field private _ number Compute from vim mode. See |OpMode|
----@field line number Line motion (ie. `gc2j`)
----@field char number Character/left-right motion (ie. `gc2j`)
----@field block number Visual operator-pending motion
----@field v number Visual motion
----@field V number Visual-line motion
+---@field private _ integer Compute from vim mode. See |OpMode|
+---@field line integer Line motion (ie. 'gc2j')
+---@field char integer Character/left-right motion (ie. 'gc2w')
+---@field block integer Visual operator-pending motion
+---@field v integer Visual motion (ie. 'v3jgc')
+---@field V integer Visual-line motion (ie. 'V10kgc')
---An object containing comment motions
---@type CommentMotion
@@ -55,53 +55,43 @@ U.cmotion = {
V = 5,
}
+---@private
---Check whether the line is empty
----@param ln string
+---@param iter string|string[]
---@return boolean
-function U.is_empty(ln)
- return #ln == 0
+function U.is_empty(iter)
+ return #iter == 0
end
----Takes out the leading indent from lines
----@param s string
----@return string string Indent chars
----@return number string Length of the indent chars
-function U.grab_indent(s)
- local _, len, indent = s:find('^(%s*)')
- return indent, len
-end
-
----Helper to get padding character and regex pattern
----NOTE: Use a function for conditional padding
----@param flag boolean|fun():boolean
----@return string string Padding chars
----@return string string Padding pattern
-function U.get_padding(flag)
- if not U.is_fn(flag) then
- return '', ''
- end
- return ' ', '%s?'
+---@private
+---Get the length of the indentation
+---@param str string
+---@return integer integer Length of indent chars
+function U.indent_len(str)
+ local _, len = string.find(str, '^%s*')
+ return len
end
--- FIXME This prints `a` in i_CTRL-o
----Moves the cursor and enters INSERT mode
----@param row number Starting row
----@param col number Ending column
-function U.move_n_insert(row, col)
- A.nvim_win_set_cursor(0, { row, col })
- A.nvim_feedkeys('a', 'ni', true)
+---@private
+---Helper to get padding character
+---@param flag boolean
+---@return string string
+function U.get_pad(flag)
+ return flag and ' ' or ''
end
----Convert the string to a escaped string, if given
----@param str string
----@return string|boolean
-function U.escape(str)
- return str and vim.pesc(str)
+---@private
+---Helper to get padding pattern
+---@param flag boolean
+---@return string string
+function U.get_padpat(flag)
+ return flag and '%s?' or ''
end
+---@private
---Call a function if exists
----@param fn function Wanna be function
----@return boolean|string
+---@param fn unknown|fun():unknown Wanna be function
+---@return unknown
function U.is_fn(fn, ...)
if type(fn) == 'function' then
return fn(...)
@@ -109,12 +99,13 @@ function U.is_fn(fn, ...)
return fn
end
+---@private
---Check if the given line is ignored or not with the given pattern
---@param ln string Line to be ignored
---@param pat string Lua regex
---@return boolean
function U.ignore(ln, pat)
- return pat and ln:find(pat) ~= nil
+ return pat and string.find(ln, pat) ~= nil
end
---Get region for line movement or visual selection
@@ -127,26 +118,18 @@ function U.get_region(opmode)
return { srow = row, scol = 0, erow = row, ecol = 0 }
end
- local m = A.nvim_buf_get_mark
- local buf = 0
- local sln, eln
-
- if string.match(opmode, '[vV]') then
- sln, eln = m(buf, '<'), m(buf, '>')
- else
- sln, eln = m(buf, '['), m(buf, ']')
- end
+ local marks = string.match(opmode, '[vV]') and { '<', '>' } or { '[', ']' }
+ local sln, eln = A.nvim_buf_get_mark(0, marks[1]), A.nvim_buf_get_mark(0, marks[2])
return { srow = sln[1], scol = sln[2], erow = eln[1], ecol = eln[2] }
end
---Get lines from the current position to the given count
----@param count number
+---@param count integer Probably 'vim.v.count'
---@return CommentLines
---@return CommentRange
function U.get_count_lines(count)
- local pos = A.nvim_win_get_cursor(0)
- local srow = pos[1]
+ local srow = unpack(A.nvim_win_get_cursor(0))
local erow = (srow + count) - 1
local lines = A.nvim_buf_get_lines(0, srow - 1, erow, false)
@@ -166,28 +149,24 @@ function U.get_lines(range)
end
---Validates and unwraps the given commentstring
----@param cstr string
----@return string|boolean
----@return string|boolean
+---@param cstr string See 'commentstring'
+---@return string string Left side of the commentstring
+---@return string string Right side of the commentstring
function U.unwrap_cstr(cstr)
- local lcs, rcs = cstr:match('(.*)%%s(.*)')
+ local left, right = string.match(cstr, '(.*)%%s(.*)')
- if not (lcs or rcs) then
- return vim.notify(
- ("[Comment] Invalid commentstring - %q. Run ':h commentstring' for help."):format(cstr),
- vim.log.levels.ERROR
- )
- end
+ assert(
+ (left or right),
+ string.format("[Comment] Invalid commentstring - %q. Run ':h commentstring' for help.", cstr)
+ )
- -- Return false if a part is empty, otherwise trim it
- -- Bcz it is better to deal with boolean rather than checking empty string length everywhere
- return not U.is_empty(lcs) and vim.trim(lcs), not U.is_empty(rcs) and vim.trim(rcs)
+ return vim.trim(left), vim.trim(right)
end
----Unwraps the commentstring by taking it from the following places
---- 1. pre_hook (optionally a string can be returned)
---- 2. ft_table (extra commentstring table in the plugin)
---- 3. commentstring (already set or added in pre_hook)
+---Parses commentstring from the following places in the respective order
+--- 1. pre_hook - commentstring returned from the function
+--- 2. ft.lua - commentstring table bundled with the plugin
+--- 3. commentstring - Neovim's native. See 'commentstring'
---@param cfg CommentConfig
---@param ctx CommentCtx
---@return string string Left side of the commentstring
@@ -203,62 +182,170 @@ function U.parse_cstr(cfg, ctx)
return U.unwrap_cstr(cstr)
end
----Converts the given string into a commented string
----@param ln string String that needs to be commented
----@param lcs string Left side of the commentstring
----@param rcs string Right side of the commentstring
----@param padding string Padding chars b/w comment and line
----@param min_indent? string Minimum indent to use with multiple lines
----@return string string Commented string
-function U.comment_str(ln, lcs, rcs, padding, min_indent)
- if U.is_empty(ln) then
- return (min_indent or '') .. ((lcs or '') .. (rcs or ''))
- end
-
- local indent, chars = ln:match('^(%s*)(.*)')
-
- local lcs_new = lcs and lcs .. padding or ''
- local rcs_new = rcs and padding .. rcs or ''
+---Returns a closure which is used to do comments
+---
+---If given {string[]} to the closure then it will do blockwise comment
+---else linewise comment will be done with the given {string}
+---@param left string Left side of the commentstring
+---@param right string Right side of the commentstring
+---@param padding boolean Is padding enabled?
+---@param scol? integer Starting column
+---@param ecol? integer Ending column
+---@return fun(line:string|string[]):string
+function U.commenter(left, right, padding, scol, ecol)
+ local pad = U.get_pad(padding)
+ local ll = U.is_empty(left) and left or (left .. pad)
+ local rr = U.is_empty(right) and right or (pad .. right)
+ local empty = string.rep(' ', scol or 0) .. left .. right
+ local is_lw = scol and not ecol
- local pos = #(min_indent or indent)
- local l_indent = indent:sub(0, pos) .. lcs_new .. indent:sub(pos + 1)
-
- return l_indent .. chars .. rcs_new
+ return function(line)
+ ------------------
+ -- for linewise --
+ ------------------
+ if is_lw then
+ if U.is_empty(line) then
+ return empty
+ end
+ -- line == 0 -> start from 0 col
+ if scol == 0 then
+ return (ll .. line .. rr)
+ end
+ local first = string.sub(line, 0, scol)
+ local last = string.sub(line, scol + 1, -1)
+ return table.concat({ first, ll, last, rr })
+ end
+
+ -------------------
+ -- for blockwise --
+ -------------------
+ if type(line) == 'table' then
+ local first, last = line[1], line[#line]
+ -- If both columns are given then we can assume it's a partial block
+ if scol and ecol then
+ local sfirst = string.sub(first, 0, scol)
+ local slast = string.sub(first, scol + 1, -1)
+ local efirst = string.sub(last, 0, ecol + 1)
+ local elast = string.sub(last, ecol + 2, -1)
+ line[1] = sfirst .. ll .. slast
+ line[#line] = efirst .. rr .. elast
+ else
+ line[1] = U.is_empty(first) and left or string.gsub(first, '^(%s*)', '%1' .. ll)
+ line[#line] = U.is_empty(last) and right or (last .. rr)
+ end
+ return line
+ end
+
+ --------------------------------
+ -- for current-line blockwise --
+ --------------------------------
+ local first = string.sub(line, 0, scol)
+ local mid = string.sub(line, scol + 1, ecol + 1)
+ local last = string.sub(line, ecol + 2, -1)
+ return table.concat({ first, ll, mid, rr, last })
+ end
end
----Converts the given string into a uncommented string
----@param ln string Line that needs to be uncommented
----@param lcs_esc string (Escaped) Left side of the commentstring
----@param rcs_esc string (Escaped) Right side of the commentstring
----@param pp string Padding pattern. See |U.get_padding|
----@return string string Uncommented string
-function U.uncomment_str(ln, lcs_esc, rcs_esc, pp)
- local ll = lcs_esc and lcs_esc .. pp or ''
- local rr = rcs_esc and rcs_esc .. '$?' or ''
-
- local indent, chars = ln:match('(%s*)' .. ll .. '(.*)' .. rr)
-
- -- If the line (after cstring) is empty then just return ''
- -- bcz when uncommenting multiline this also doesn't preserve leading whitespace as the line was previously empty
- if U.is_empty(chars) then
- return ''
- end
+---Returns a closure which is used to uncomment a line
+---
+---If given {string[]} to the closure then it will block uncomment
+---else linewise uncomment will be done with the given {string}
+---@param left string Left side of the commentstring
+---@param right string Right side of the commentstring
+---@param padding boolean Is padding enabled?
+---@param scol? integer Starting column
+---@param ecol? integer Ending column
+---@return fun(line:string|string[]):string
+function U.uncommenter(left, right, padding, scol, ecol)
+ local pp, plen = U.get_padpat(padding), padding and 1 or 0
+ local left_len, right_len = #left + plen, #right + plen
+ local ll = U.is_empty(left) and left or vim.pesc(left) .. pp
+ local rr = U.is_empty(right) and right or pp .. vim.pesc(right)
+ local is_lw = not (scol and scol)
+ local pattern = is_lw and '^(%s*)' .. ll .. '(.-)' .. rr .. '$'
- -- When padding is enabled then trim one trailing space char
- return indent .. chars:gsub(pp .. '$', '')
+ return function(line)
+ -------------------
+ -- for blockwise --
+ -------------------
+ if type(line) == 'table' then
+ local first, last = line[1], line[#line]
+ -- If both columns are given then we can assume it's a partial block
+ if scol and ecol then
+ local sfirst = string.sub(first, 0, scol)
+ local slast = string.sub(first, scol + left_len + 1, -1)
+ local efirst = string.sub(last, 0, ecol - right_len + 1)
+ local elast = string.sub(last, ecol + 2, -1)
+ line[1] = sfirst .. slast
+ line[#line] = efirst .. elast
+ else
+ line[1] = string.gsub(first, '^(%s*)' .. ll, '%1')
+ line[#line] = string.gsub(last, rr .. '$', '')
+ end
+ return line
+ end
+
+ ------------------
+ -- for linewise --
+ ------------------
+ if is_lw then
+ local a, b, c = string.match(line, pattern)
+ -- If there is nothing after LHS then just return ''
+ -- bcz the line previously (before comment) was empty
+ return U.is_empty(b) and b or a .. b .. (c or '')
+ end
+
+ --------------------------------
+ -- for current-line blockwise --
+ --------------------------------
+ local first = string.sub(line, 0, scol)
+ local mid = string.sub(line, scol + left_len + 1, ecol - right_len + 1)
+ local last = string.sub(line, ecol + 2, -1)
+ return first .. mid .. last
+ end
end
---Check if the given string is commented or not
----@param lcs_esc string (Escaped) Left side of the commentstring
----@param rcs_esc string (Escaped) Right side of the commentstring
----@param pp string Padding pattern. See |U.get_padding|
----@return fun(line:string):boolean
-function U.is_commented(lcs_esc, rcs_esc, pp)
- local ll = lcs_esc and '^%s*' .. lcs_esc .. pp or ''
- local rr = rcs_esc and pp .. rcs_esc .. '$' or ''
+---
+---If given {string[]} to the closure, it will check the first and last line
+---with LHS and RHS of commentstring respectively else it will check the given
+---line with LHS and RHS (if given) of the commenstring
+---@param left string Left side of the commentstring
+---@param right string Right side of the commentstring
+---@param padding boolean Is padding enabled?
+---@param scol? integer Starting column
+---@param ecol? integer Ending column
+---@return fun(line:string|string[]):boolean
+function U.is_commented(left, right, padding, scol, ecol)
+ local pp = U.get_padpat(padding)
+ local ll = U.is_empty(left) and left or '^%s*' .. vim.pesc(left) .. pp
+ local rr = U.is_empty(right) and right or pp .. vim.pesc(right) .. '$'
+ local pattern = ll .. '.-' .. rr
+ local is_full = scol == nil or ecol == nil
return function(line)
- return line:find(ll .. '(.-)' .. rr)
+ -------------------
+ -- for blockwise --
+ -------------------
+ if type(line) == 'table' then
+ local first, last = line[1], line[#line]
+ if is_full then
+ return string.find(first, ll) and string.find(last, rr)
+ end
+ return string.find(string.sub(first, scol + 1, -1), ll) and string.find(string.sub(last, 0, ecol + 1), rr)
+ end
+
+ ------------------
+ -- for linewise --
+ ------------------
+ if is_full then
+ return string.find(line, pattern)
+ end
+
+ --------------------------------
+ -- for current-line blockwise --
+ --------------------------------
+ return string.find(string.sub(line, scol + 1, ecol + 1), pattern)
end
end