@@ -5,7 +5,7 @@ local utils = lazy.require("diffview.utils") ---@module "diffview.utils"
55
66local M = {}
77
8- local short_flag_pat = { " ^%-(%a)=?(.*) " , " ^%+ (%a)=?(.*)" }
8+ local short_flag_pat = { " ^[-+] (%a)=?(.*)" }
99local long_flag_pat = { " ^%-%-(%a[%a%d-]*)=?(.*)" , " ^%+%+(%a[%a%d-]*)=?(.*)" }
1010
1111--- @class ArgObject : diffview.Object
@@ -231,55 +231,92 @@ function M.split_ex_range(arg)
231231end
232232
233233--- @class CmdLineContext
234- --- @field args string[] The complete list of arguments.
235- --- @field arg_lead string
236- --- @field argidx integer Index of the current argument.
237- --- @field divideridx integer
238- --- @field range string ? Ex command range.
239- --- @field between boolean The current position is between two arguments.
240-
241- --- Scan an EX command string and split it into individual args.
234+ --- @field cmd_line string
235+ --- @field args string[] # The tokenized list of arguments.
236+ --- @field raw_args string[] # The unprocessed list of arguments. Contains syntax characters, such as quotes.
237+ --- @field arg_lead string # The leading part of the current argument.
238+ --- @field lead_quote string ? # If present: the quote character used for the current argument.
239+ --- @field cur_pos integer # The cursor position in the command line.
240+ --- @field argidx integer # Index of the current argument.
241+ --- @field divideridx integer # The index of the end-of-options token. (default: math.huge)
242+ --- @field range string ? # Ex command range.
243+ --- @field between boolean # The current position is between two arguments.
244+
245+ --- @class arg_parser.scan.Opt
246+ --- @field cur_pos integer # The current cursor position in the command line.
247+ --- @field allow_quoted boolean # Everything between a pair of quotes should be treated as part of a single argument. (default: true)
248+ --- @field allow_ex_range boolean # The command line may contain an EX command range. (default: false)
249+
250+ --- Tokenize a command line string.
242251--- @param cmd_line string
243- --- @param cur_pos number
252+ --- @param opt ? arg_parser.scan.Opt
244253--- @return CmdLineContext
245- function M .scan_ex_args (cmd_line , cur_pos )
254+ function M .scan (cmd_line , opt )
255+ opt = vim .tbl_extend (" keep" , opt or {}, {
256+ cur_pos = # cmd_line + 1 ,
257+ allow_quoted = true ,
258+ allow_ex_range = false ,
259+ }) --[[ @as arg_parser.scan.Opt ]]
260+
246261 local args = {}
262+ local raw_args = {}
247263 local arg_lead
248264 local divideridx = math.huge
249265 local argidx
250266 local between = false
251- local arg = " "
267+ local cur_quote , lead_quote
268+ local arg , raw_arg = " " , " "
252269
253270 local h , i = - 1 , 1
254271
255272 while i <= # cmd_line do
256273 local char = cmd_line :sub (i , i )
257274
258- if not argidx and i > cur_pos then
275+ if not argidx and i > opt . cur_pos then
259276 argidx = # args + 1
260277 arg_lead = arg
261- if h < cur_pos then between = true end
278+ lead_quote = cur_quote
279+ if h < opt .cur_pos then between = true end
262280 end
263281
264282 if char == " \\ " then
265283 arg = arg .. char
284+ raw_arg = raw_arg .. char
266285 if i < # cmd_line then
267286 i = i + 1
268287 arg = arg .. cmd_line :sub (i , i )
288+ raw_arg = raw_arg .. cmd_line :sub (i , i )
269289 end
270290 h = i
291+ elseif cur_quote then
292+ if char == cur_quote then
293+ cur_quote = nil
294+ else
295+ arg = arg .. char
296+ end
297+ raw_arg = raw_arg .. char
298+ h = i
299+ elseif opt .allow_quoted and (char == [[ ']] or char == [[ "]] ) then
300+ cur_quote = char
301+ raw_arg = raw_arg .. char
302+ h = i
271303 elseif char :match (" %s" ) then
272304 if arg ~= " " then
273305 table.insert (args , arg )
274306 if arg == " --" and i - 1 < # cmd_line then
275307 divideridx = # args
276308 end
277309 end
310+ if raw_arg ~= " " then
311+ table.insert (raw_args , raw_arg )
312+ end
278313 arg = " "
314+ raw_arg = " "
279315 -- Skip whitespace
280316 i = i + cmd_line :sub (i , - 1 ):match (" ^%s+()" ) - 2
281317 else
282318 arg = arg .. char
319+ raw_arg = raw_arg .. char
283320 h = i
284321 end
285322
@@ -288,7 +325,11 @@ function M.scan_ex_args(cmd_line, cur_pos)
288325
289326 if # arg > 0 then
290327 table.insert (args , arg )
291- if not arg_lead then arg_lead = arg end
328+ table.insert (raw_args , raw_arg )
329+ if not arg_lead then
330+ arg_lead = arg
331+ lead_quote = cur_quote
332+ end
292333
293334 if arg == " --" and cmd_line :sub (# cmd_line , # cmd_line ) ~= " -" then
294335 divideridx = # args
@@ -305,110 +346,75 @@ function M.scan_ex_args(cmd_line, cur_pos)
305346 local range
306347
307348 if # args > 0 then
308- range , args [1 ] = M .split_ex_range (args [1 ])
349+ if opt .allow_ex_range then
350+ range , args [1 ] = M .split_ex_range (args [1 ])
351+ _ , raw_args [1 ] = M .split_ex_range (raw_args [1 ])
352+ end
353+
309354 if args [1 ] == " " then
310355 table.remove (args , 1 )
356+ table.remove (raw_args , 1 )
311357 argidx = math.max (argidx - 1 , 1 )
312358 divideridx = math.max (divideridx - 1 , 1 )
313359 end
314360 end
315361
316362 return {
363+ cmd_line = cmd_line ,
317364 args = args ,
365+ raw_args = raw_args ,
318366 arg_lead = arg_lead or " " ,
367+ lead_quote = lead_quote ,
368+ cur_pos = opt .cur_pos ,
319369 argidx = argidx ,
320370 divideridx = divideridx ,
321371 range = range ~= " " and range or nil ,
322372 between = between ,
323373 }
324374end
325375
326- --- Scan a shell-like string and split it into individual args. This scanner
327- --- understands quoted args.
328- --- @param cmd_line string
329- --- @param cur_pos number
330- --- @return CmdLineContext
331- function M .scan_sh_args (cmd_line , cur_pos )
332- local args = {}
333- local arg_lead
334- local divideridx = math.huge
335- local argidx
336- local between = false
337- local cur_quote
338- local arg = " "
339-
340- local h , i = - 1 , 1
376+ --- Filter completion candidates.
377+ --- @param arg_lead string
378+ --- @param candidates string[]
379+ --- @return string[]
380+ function M .filter_candidates (arg_lead , candidates )
381+ arg_lead , _ = vim .pesc (arg_lead )
341382
342- while i <= # cmd_line do
343- local char = cmd_line :sub (i , i )
383+ return vim .tbl_filter (function (item )
384+ return item :match (arg_lead )
385+ end , candidates )
386+ end
344387
345- if not argidx and i > cur_pos then
346- argidx = # args + 1
347- arg_lead = arg
348- if h < cur_pos then between = true end
349- end
388+ --- Process completion candidates.
389+ --- @param candidates string[]
390+ --- @param ctx CmdLineContext
391+ --- @param input_cmp boolean ? Completion for | input ()| .
392+ --- @return string[]
393+ function M .process_candidates (candidates , ctx , input_cmp )
394+ if not candidates then return {} end
350395
351- if char == " \\ " then
352- if i < # cmd_line then
353- i = i + 1
354- arg = arg .. cmd_line :sub (i , i )
355- end
356- h = i
357- elseif cur_quote then
358- if char == cur_quote then
359- cur_quote = nil
360- else
361- arg = arg .. char
362- end
363- h = i
364- elseif char == [[ ']] or char == [[ "]] then
365- cur_quote = char
366- h = i
367- elseif char :match (" %s" ) then
368- if arg ~= " " then
369- table.insert (args , arg )
370- if arg == " --" and i - 1 < # cmd_line then
371- divideridx = # args
372- end
373- end
374- arg = " "
375- -- Skip whitespace
376- i = i + cmd_line :sub (i , - 1 ):match (" ^%s+()" ) - 2
377- else
378- arg = arg .. char
379- h = i
380- end
396+ local cmd_lead = " "
397+ local ex_lead = (ctx .lead_quote or " " ) .. ctx .arg_lead
381398
382- i = i + 1
399+ if ctx .arg_lead and ctx .arg_lead :find (" [^\\ ]%s" ) then
400+ ex_lead = (ctx .lead_quote or " " ) .. ctx .arg_lead :match (" .*[^\\ ]%s(.*)" )
383401 end
384402
385- if cur_quote then
386- error ( " The given command line contains a non-terminated string! " )
403+ if input_cmp then
404+ cmd_lead = ctx . cmd_line : sub ( 1 , ctx . cur_pos - # ex_lead )
387405 end
388406
389- if # arg > 0 then
390- table.insert (args , arg )
391- if not arg_lead then arg_lead = arg end
392-
393- if arg == " --" and cmd_line :sub (# cmd_line , # cmd_line ) ~= " -" then
394- divideridx = # args
407+ local ret = vim .tbl_map (function (v )
408+ if v :match (" ^" .. vim .pesc (ctx .arg_lead )) then
409+ return cmd_lead .. ex_lead .. v :sub (# ctx .arg_lead + 1 )
410+ elseif input_cmp then
411+ return cmd_lead .. v
395412 end
396- end
397413
398- if not argidx then
399- argidx = # args
400- if cmd_line :sub (# cmd_line , # cmd_line ):match (" %s" ) then
401- argidx = argidx + 1
402- end
403- end
414+ return (ctx .lead_quote or " " ) .. v
415+ end , candidates )
404416
405- return {
406- args = args ,
407- arg_lead = arg_lead or " " ,
408- argidx = argidx ,
409- divideridx = divideridx ,
410- between = between ,
411- }
417+ return M .filter_candidates (cmd_lead .. ex_lead , ret )
412418end
413419
414420function M .ambiguous_bool (value , default , truthy , falsy )
0 commit comments