Skip to content

Commit c51c1d8

Browse files
committed
util: Add types & docs for str utils
1 parent 3732756 commit c51c1d8

1 file changed

Lines changed: 86 additions & 51 deletions

File tree

lua/luasnip/util/str.lua

Lines changed: 86 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
-- Some string processing utility functions
22
local M = {}
33

4-
---In-place dedents strings in lines.
4+
--- In-place dedents strings in lines.
55
---@param lines string[].
66
local function dedent(lines)
77
if #lines > 0 then
88
local ind_size = math.huge
99
for i, _ in ipairs(lines) do
1010
local i1, i2 = lines[i]:find("^%s*[^%s]")
11-
if i1 and i2 < ind_size then
11+
if i1 and i2 and i2 < ind_size then
1212
ind_size = i2
1313
end
1414
end
@@ -18,7 +18,7 @@ local function dedent(lines)
1818
end
1919
end
2020

21-
---Convert string `from` to unit indent
21+
--- In-place convert string `from` to unit indent in lines.
2222
---@param lines string[]
2323
---@param from string
2424
---@param unit_indent string
@@ -51,15 +51,17 @@ local function convert_indent(lines, from, unit_indent)
5151
end
5252
end
5353

54-
---Applies opts to lines.
55-
---lines is modified in-place.
56-
---@param lines string[].
57-
---@param options table, required, can have values:
58-
--- - trim_empty: removes empty first and last lines.
59-
--- - dedent: removes indent common to all lines.
60-
--- - indent_string: an unit indent at beginning of each line after applying `dedent`, default empty string (disabled)
61-
function M.process_multiline(lines, options)
62-
if options.trim_empty then
54+
---@class LuaSnip.Opts.Str.MultilineProcess
55+
---@field trim_empty? boolean Whether to remove whitespace-only first/last lines
56+
---@field dedent? boolean Whether to remove all common indent in `str`.
57+
---@field indent_string? string When set, will convert `indent_string` at
58+
--- beginning of each line to unit indent ('\t') after applying `dedent`.
59+
60+
--- In-place process lines with given opts.
61+
---@param lines string[]
62+
---@param opts LuaSnip.Opts.Str.MultilineProcess
63+
function M.process_multiline(lines, opts)
64+
if opts.trim_empty then
6365
if lines[1]:match("^%s*$") then
6466
table.remove(lines, 1)
6567
end
@@ -68,21 +70,28 @@ function M.process_multiline(lines, options)
6870
end
6971
end
7072

71-
if options.dedent then
73+
if opts.dedent then
7274
dedent(lines)
7375
end
7476

75-
if options.indent_string and #options.indent_string > 0 then
76-
convert_indent(lines, options.indent_string, "\t")
77+
if opts.indent_string and #opts.indent_string > 0 then
78+
convert_indent(lines, opts.indent_string, "\t")
7779
end
7880
end
7981

82+
--- Remove common indentation from the given string.
83+
---@param s string
84+
---@return string
8085
function M.dedent(s)
8186
local lst = vim.split(s, "\n")
8287
dedent(lst)
8388
return table.concat(lst, "\n")
8489
end
8590

91+
--- Convert string `indent_string` to unit indent (\t) in given string.
92+
---@param s string
93+
---@param indent_string string
94+
---@return string
8695
function M.convert_indent(s, indent_string)
8796
local lst = vim.split(s, "\n")
8897
convert_indent(lst, indent_string, "\t")
@@ -101,11 +110,12 @@ local function is_escaped(s, indx)
101110
return count % 2 == 1
102111
end
103112

104-
--- return position of next (relative to `start`) unescaped occurence of
113+
--- Return position of next (relative to `start`) unescaped occurence of
105114
--- `target` in `s`.
106115
---@param s string
107116
---@param target string
108-
---@param start number
117+
---@param start integer
118+
---@return integer?
109119
local function find_next_unescaped(s, target, start)
110120
while true do
111121
local from = s:find(target, start, true)
@@ -125,7 +135,7 @@ end
125135
---@param s string
126136
---@param left string
127137
---@param right string
128-
---@return function: iterator, returns pairs from,to.
138+
---@return fun(): (integer?, integer?) An iterator returning pairs from,to.
129139
function M.unescaped_pairs(s, left, right)
130140
local search_from = 1
131141

@@ -144,6 +154,7 @@ function M.unescaped_pairs(s, left, right)
144154
end
145155
end
146156

157+
-- FIXME(@L3MON4D3): not used anywhere?
147158
function M.aupatescape(s)
148159
if vim.fn.has("win32") == 1 or vim.fn.has("win64") == 1 then
149160
-- windows: replace \ with / for au-pattern.
@@ -153,20 +164,26 @@ function M.aupatescape(s)
153164
return vim.fn.fnameescape(escaped)
154165
end
155166

167+
--- Sanitize the given string (e.g. \r)
168+
---@param str string
169+
---@return string
156170
function M.sanitize(str)
157-
return str:gsub("%\r", "")
171+
local ret = str:gsub("%\r", "")
172+
return ret -- note: local var required for correct typing
158173
end
159174

160-
-- requires that from and to are within the region of str.
161-
-- str is treated as a 0,0-indexed, and the character at `to` is excluded from
162-
-- the result.
163-
-- `from` may not be before `to`.
164-
function M.multiline_substr(str, from, to)
175+
--- Extract a rectangular block of lines in a multiline string area.
176+
---@param lines string[]
177+
---@param from LuaSnip.Pos00 From this position, MUST be within `lines`.
178+
---@param to LuaSnip.Pos00 To this position (excluded), MUST be within `lines`
179+
--- and before `from`.
180+
---@return string[]
181+
function M.multiline_substr(lines, from, to)
165182
local res = {}
166183

167184
-- include all rows
168185
for i = from[1], to[1] do
169-
table.insert(res, str[i + 1])
186+
table.insert(res, lines[i + 1])
170187
end
171188

172189
-- trim text before from and after to.
@@ -179,35 +196,42 @@ function M.multiline_substr(str, from, to)
179196
return res
180197
end
181198

182-
function M.multiline_upper(str)
183-
for i, s in ipairs(str) do
184-
str[i] = s:upper()
199+
--- In-place uppercase all text in `lines`
200+
---@param lines string[]
201+
function M.multiline_upper(lines)
202+
for i, s in ipairs(lines) do
203+
lines[i] = s:upper()
185204
end
186205
end
187-
function M.multiline_lower(str)
188-
for i, s in ipairs(str) do
189-
str[i] = s:lower()
206+
207+
--- In-place lowercase all text in `lines`
208+
---@param lines string[]
209+
function M.multiline_lower(lines)
210+
for i, s in ipairs(lines) do
211+
lines[i] = s:lower()
190212
end
191213
end
192214

193215
-- modifies strmod
216+
-- FIXME(@L3MON4D3): not used anywhere?
194217
function M.multiline_append(strmod, strappend)
195218
strmod[#strmod] = strmod[#strmod] .. strappend[1]
196219
for i = 2, #strappend do
197220
table.insert(strmod, strappend[i])
198221
end
199222
end
200223

201-
-- turn a row+col-offset for a multiline-string (string[]) (where the column is
202-
-- given in bytes and 0-based) into an offset (in bytes, 1-based) for
203-
-- the \n-concatenated version of that string.
224+
--- Turns a row+col-offset for a multiline-string (string[]) (where the column is
225+
--- given in bytes and 0-based) into an offset (in bytes, 1-based) for
226+
--- the \n-concatenated version of that string.
204227
---
205-
---@param str string[], a multiline string
206-
---@param pos LuaSnip.ApiPosition, an api-position relative to the start of str.
207-
function M.multiline_to_byte_offset(str, pos)
208-
if pos[1] < 0 or pos[1] + 1 > #str or pos[2] < 0 then
228+
---@param lines string[] a multiline string
229+
---@param pos LuaSnip.ApiPosition an api-position relative to the start of str.
230+
---@return integer?
231+
function M.multiline_to_byte_offset(lines, pos)
232+
if pos[1] < 0 or pos[1] + 1 > #lines or pos[2] < 0 then
209233
-- pos is trivially (row negative or beyond str, or col negative)
210-
-- outside of str, can't represent position in str.
234+
-- outside of lines, can't represent position in lines.
211235
-- col-wise outside will be determined later, but we want this
212236
-- precondition for following code.
213237
return nil
@@ -216,12 +240,12 @@ function M.multiline_to_byte_offset(str, pos)
216240
local byte_pos = 0
217241
for i = 1, pos[1] do
218242
-- increase index by full lines, don't forget +1 for \n.
219-
byte_pos = byte_pos + #str[i] + 1
243+
byte_pos = byte_pos + #lines[i] + 1
220244
end
221245

222246
-- allow positions one beyond the last character for all lines (even the
223247
-- last line).
224-
if pos[2] >= #str[pos[1] + 1] + 1 then
248+
if pos[2] >= #lines[pos[1] + 1] + 1 then
225249
-- in this case, pos is outside of the multiline-region.
226250
return nil
227251
end
@@ -233,16 +257,18 @@ function M.multiline_to_byte_offset(str, pos)
233257
return byte_pos + 1
234258
end
235259

236-
-- inverse of multiline_to_byte_offset, 1-based byte to 0,0-based row,column.
237-
---@param str string[], the multiline string
238-
---@param byte_pos number, a 1-based index into the \n-concatenated `str`.
239-
function M.byte_to_multiline_offset(str, byte_pos)
260+
--- Convert a 1-based byte index in a multiline string to 0,0-based row,column.
261+
--- (It is functionally the inverse of multiline_to_byte_offset)
262+
---@param lines string[] the multiline string
263+
---@param byte_pos number 1-based index into the \n-concatenated `lines`.
264+
---@return LuaSnip.Pos00?
265+
function M.byte_to_multiline_offset(lines, byte_pos)
240266
if byte_pos < 0 then
241267
return nil
242268
end
243269

244270
local byte_pos_so_far = 0
245-
for i, line in ipairs(str) do
271+
for i, line in ipairs(lines) do
246272
-- line-length + \n.
247273
local line_i_end = byte_pos_so_far + #line + 1
248274
if byte_pos <= line_i_end then
@@ -256,27 +282,36 @@ end
256282
-- string-operations implemented according to
257283
-- https://github.com/microsoft/vscode/blob/71c221c532996c9976405f62bb888283c0cf6545/src/vs/editor/contrib/snippet/browser/snippetParser.ts#L372-L415
258284
-- such that they can be used for snippet-transformations in vscode-snippets.
285+
---@param str string
286+
---@return string
259287
local function capitalize(str)
260288
-- uppercase first character.
261-
return str:gsub("^.", string.upper)
289+
local ret = str:gsub("^.", string.upper)
290+
return ret -- note: local var required for correct typing
262291
end
292+
---@param str string
293+
---@return string
263294
local function pascalcase(str)
264295
local pascalcased = ""
265296
for match in str:gmatch("[a-zA-Z0-9]+") do
266297
pascalcased = pascalcased .. capitalize(match)
267298
end
268299
return pascalcased
269300
end
301+
---@param str string
302+
---@return string
303+
local function camelcase(str)
304+
-- same as pascalcase, but first character lowercased.
305+
local ret = pascalcase(str):gsub("^.", string.lower)
306+
return ret -- note: local var required for correct typing
307+
end
270308

271309
M.vscode_string_modifiers = {
272310
upcase = string.upper,
273311
downcase = string.lower,
274312
capitalize = capitalize,
275313
pascalcase = pascalcase,
276-
camelcase = function(str)
277-
-- same as pascalcase, but first character lowercased.
278-
return pascalcase(str):gsub("^.", string.lower)
279-
end,
314+
camelcase = camelcase,
280315
}
281316

282317
return M

0 commit comments

Comments
 (0)