Skip to content
This repository was archived by the owner on Apr 5, 2025. It is now read-only.

Commit 09b4f46

Browse files
Fix completion of command when there is more text after
1 parent 0a6786e commit 09b4f46

File tree

7 files changed

+104
-82
lines changed

7 files changed

+104
-82
lines changed

src/Fargo/Fargo.fs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ module Fargo =
127127
if Usage.isStop pos x then
128128
Usage.complete usage x.Text
129129
elif Usage.isMatch usage x then
130-
if pos <= y.Extent.End then
130+
if pos > x.Extent.End && pos <= y.Extent.End then
131131
(complete y.Text tokens, true)
132132
else
133133
[], false
@@ -376,11 +376,15 @@ module Fargo =
376376
let complete pos tokens =
377377
match px tokens with
378378
| Ok x, restx, usagex ->
379-
let pf, cf = f x
380-
cf pos restx
379+
if not (Tokens.contains pos tokens) || Tokens.contains pos restx then
380+
let _pf, cf = f x
381+
cf pos restx
382+
else
383+
cx pos tokens
381384
| Error _, _, _ ->
382385
cx pos tokens
383386
parse, complete
387+
384388
let ret (x: 'a) : Arg<'a> =
385389
let parse tokens =
386390
Ok x, tokens, Usages.empty
@@ -804,7 +808,7 @@ Register-ArgumentCompleter -Native -CommandName %s -ScriptBlock {
804808
}
805809

806810

807-
let run appName ((p,c): Arg<'a>) (cmdLine: string[]) (f: CancellationToken ->'a -> Task<int>) : int =
811+
let run appName ((p,c): Arg<'a>) (cmdLine: string[]) (f: CancellationToken -> 'a -> Task<int>) : int =
808812
use cts = new CancellationTokenSource()
809813
let mutable graceful = true
810814
Console.CancelKeyPress
@@ -817,15 +821,15 @@ Register-ArgumentCompleter -Native -CommandName %s -ScriptBlock {
817821
else
818822
printfn $"{Colors.red}[Ctrl+C]Force stop{Colors.def}"
819823
)
820-
let tokens = Token.ofCmdLine cmdLine
824+
let tokens = Tokens.ofCmdLine cmdLine
821825
let runner =
822826
innerRun (pRun appName) tokens (fun cmd ->
823827
task {
824828
match cmd with
825829
| TopComplete(pos, rest) ->
826830
let cmdTokens =
827831
match rest with
828-
| [x] -> Token.ofString x.Text
832+
| [x] -> Tokens.ofString x.Text
829833
| _ -> rest
830834
for result in complete (fargo { do! pRemoveAppName appName
831835
return! (p,c)}) pos cmdTokens do

src/Fargo/Token.fs

Lines changed: 43 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ module Extent =
3737
pos >= extent.Start && pos <= extent.End
3838

3939
module Token =
40-
open Microsoft.FSharp.Core.CompilerServices
4140

4241
let inline extent s e = { Start = s; End = e}
4342

@@ -54,41 +53,44 @@ module Token =
5453
| Quotes q -> Some q
5554
| StartQuote _ | NoQuotes -> None
5655

56+
57+
module Tokens =
58+
open Microsoft.FSharp.Core.CompilerServices
5759
let rec private loop (input: string) (pos: int) (result: ListCollector<_> byref) =
58-
if pos >= input.Length then
59-
result.Close()
60+
if pos >= input.Length then
61+
result.Close()
62+
else
63+
if input[pos] = '"' then
64+
loopQuote '"' input pos &result
65+
elif input[pos] = '\'' then
66+
loopQuote '\'' input pos &result
6067
else
61-
if input[pos] = '"' then
62-
loopQuote '"' input pos &result
63-
elif input[pos] = '\'' then
64-
loopQuote '\'' input pos &result
65-
else
66-
match input.IndexOf(' ', pos) with
67-
| -1 ->
68-
result.Add { Text = input.Substring(pos)
69-
Extent = extent pos input.Length
70-
Quotes = NoQuotes}
71-
result.Close()
72-
| n ->
73-
let txt = input.Substring(pos, n-pos)
74-
if txt <> "" then
75-
result.Add { Text = txt
76-
Extent = extent pos n
77-
Quotes = NoQuotes }
78-
loop input (n+1) &result
68+
match input.IndexOf(' ', pos) with
69+
| -1 ->
70+
result.Add { Text = input.Substring(pos)
71+
Extent = Token.extent pos input.Length
72+
Quotes = NoQuotes}
73+
result.Close()
74+
| n ->
75+
let txt = input.Substring(pos, n-pos)
76+
if txt <> "" then
77+
result.Add { Text = txt
78+
Extent = Token.extent pos n
79+
Quotes = NoQuotes }
80+
loop input (n+1) &result
7981
and loopQuote quote (input: string) (pos: int) (result: ListCollector<_> byref) =
80-
match input.IndexOf(quote, pos+1) with
81-
| -1 ->
82-
result.Add { Text = input.Substring(pos+1)
83-
Extent = extent (pos+1) input.Length
84-
Quotes = StartQuote quote}
85-
result.Close()
86-
| n ->
87-
let txt = input.Substring(pos+1, n-pos-1)
88-
result.Add { Text = txt
89-
Extent = extent (pos+1) n
90-
Quotes = Quotes quote }
91-
loop input (n+1) &result
82+
match input.IndexOf(quote, pos+1) with
83+
| -1 ->
84+
result.Add { Text = input.Substring(pos+1)
85+
Extent = Token.extent (pos+1) input.Length
86+
Quotes = StartQuote quote}
87+
result.Close()
88+
| n ->
89+
let txt = input.Substring(pos+1, n-pos-1)
90+
result.Add { Text = txt
91+
Extent = Token.extent (pos+1) n
92+
Quotes = Quotes quote }
93+
loop input (n+1) &result
9294

9395
let ofString (input: string) =
9496
if isNull input then
@@ -109,7 +111,7 @@ module Token =
109111
Quotes '\''
110112
else
111113
NoQuotes
112-
result.Add({Text = token; Extent = extent pos (pos + token.Length); Quotes = quotes} )
114+
result.Add({Text = token; Extent = Token.extent pos (pos + token.Length); Quotes = quotes} )
113115
pos <- pos + token.Length + 1
114116

115117
result.Close()
@@ -124,20 +126,23 @@ module Token =
124126
let rec loop pos tokens =
125127
match tokens with
126128
| token :: rest ->
127-
let extent = outerExtent token
129+
let extent = Token.outerExtent token
128130
let startPad = max (extent.Start - pos) 0
129131
builder.Append(' ', startPad) |> ignore
130132

131-
startQuote token
133+
Token.startQuote token
132134
|> Option.iter (fun q -> builder.Append(q) |> ignore)
133135

134136
builder.Append(token.Text) |> ignore
135137

136-
endQuote token
138+
Token.endQuote token
137139
|> Option.iter (fun q -> builder.Append(q) |> ignore)
138140

139141

140142
loop extent.End rest
141143
| [] -> builder.ToString()
142144
loop 0 (tokens |> List.sortBy (fun t -> t.Extent))
143-
145+
146+
let contains pos (tokens: Token list) =
147+
tokens
148+
|> List.exists (Token.outerExtent >> (Extent.contains pos))

tests/Fargo.Test/AllAtOnce.fs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,11 @@ let p =
6565

6666

6767
let parse input =
68-
tryParseTokens p (Token.ofString input)
68+
tryParseTokens p (Tokens.ofString input)
6969
|> Result.mapError (fun (errs,usages) -> errs, [ for u in usages.Options -> u.Name |> Option.defaultValue "" ])
7070

7171
let complete pos input =
72-
Fargo.Run.complete p pos (Token.ofString input)
72+
Fargo.Run.complete p pos (Tokens.ofString input)
7373

7474
let complete2 pos input =
7575
Testing.withStdout (fun _ ->
@@ -217,3 +217,16 @@ let ``completion of command complete``() =
217217
=! """select"""
218218

219219

220+
[<Fact>]
221+
let ``completion of command on a token in the middle``() =
222+
complete2 10 "voice sele --voice funny"
223+
=! "select"
224+
225+
[<Fact>]
226+
let ``completion of command on a full token in the middle``() =
227+
complete2 12 "voice select --voice funny"
228+
=! "select"
229+
230+
231+
232+

tests/Fargo.Test/Completion.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ open FsCheck.Xunit
88
open DEdge.Diffract
99

1010
let complete (arg: Arg<_>) pos cmdLine =
11-
complete arg pos (Token.ofString cmdLine)
11+
complete arg pos (Tokens.ofString cmdLine)
1212

1313
let (=!) (actual:'a) (expected: 'a) = Differ.Assert(expected, actual )
1414

tests/Fargo.Test/Parsing.fs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ let (=!) (actual:'a) (expected: 'a) = Differ.Assert(expected, actual )
1010

1111

1212
let parse ((p,_): Arg<'t>) input =
13-
let result, _, _ = p (Token.ofString input)
13+
let result, _, _ = p (Tokens.ofString input)
1414
result
1515

1616
let rest ((p,c): Arg<'t>) input =
17-
let _, tokens, _ = p (Token.ofString input)
18-
Token.toString tokens
17+
let _, tokens, _ = p (Tokens.ofString input)
18+
Tokens.toString tokens
1919

2020
let usage ((p,c): Arg<'t>) input =
21-
let _, _, usages = p (Token.ofString input)
21+
let _, _, usages = p (Tokens.ofString input)
2222
usages.Options
2323

2424

@@ -904,6 +904,6 @@ module Error =
904904

905905
[<Property>]
906906
let ``Errorf always fails``(NonNull fargo) =
907-
let p = errorf (fun tokens -> Token.toString tokens )
907+
let p = errorf (fun tokens -> Tokens.toString tokens )
908908
parse p fargo
909909
=! Error [ fargo.TrimEnd(' ')]

tests/Fargo.Test/Token.fs

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,98 +17,98 @@ let qtoken q text s e = { Text = text; Extent = { Start = s; End = e}; Quotes =
1717
let sqtoken q text s e = { Text = text; Extent = { Start = s; End = e}; Quotes = StartQuote q}
1818

1919
[<Fact>]
20-
let ``Token.ofList should produce correst Start and End``() =
21-
Token.ofList ["cmd";"--arg";"value"]
20+
let ``Tokens.ofList should produce correst Start and End``() =
21+
Tokens.ofList ["cmd";"--arg";"value"]
2222
=! [ token "cmd" 0 3
2323
token "--arg" 4 9
2424
token "value" 10 15]
2525

2626
[<Fact>]
27-
let ``Token.ofList should skip null entries``() =
28-
Token.ofList ["cmd";null;"value"]
27+
let ``Tokens.ofList should skip null entries``() =
28+
Tokens.ofList ["cmd";null;"value"]
2929
=! [ token "cmd" 0 3
3030
token "value" 4 9 ]
3131
[<Fact>]
32-
let ``Token.ofString should accept null``() =
33-
Token.ofString null
32+
let ``Tokens.ofString should accept null``() =
33+
Tokens.ofString null
3434
=! []
3535

3636
[<Fact>]
37-
let ``Token.ofString should split spaces``() =
38-
Token.ofString "cmd --arg value"
37+
let ``Tokens.ofString should split spaces``() =
38+
Tokens.ofString "cmd --arg value"
3939
=! [ token "cmd" 0 3
4040
token "--arg" 4 9
4141
token "value" 10 15 ]
4242

4343
[<Fact>]
44-
let ``Token.ofString should accept multiple spaces and produce Start and End accordingly``() =
45-
Token.ofString "cmd --arg value"
44+
let ``Tokens.ofString should accept multiple spaces and produce Start and End accordingly``() =
45+
Tokens.ofString "cmd --arg value"
4646
=! [ token "cmd" 0 3
4747
token "--arg" 6 11
4848
token "value" 13 18 ]
4949

5050
[<Fact>]
51-
let ``Token.ofString should treat double quotes as a single token and remove quotes``() =
52-
Token.ofString """cmd --arg "some value" --flag"""
51+
let ``Tokens.ofString should treat double quotes as a single token and remove quotes``() =
52+
Tokens.ofString """cmd --arg "some value" --flag"""
5353
=! [ token "cmd" 0 3
5454
token "--arg" 4 9
5555
qtoken '"' "some value" 11 21
5656
token "--flag" 23 29 ]
5757

5858
[<Fact>]
59-
let ``Token.ofString should not fail on missing end double quote``() =
60-
Token.ofString """cmd --arg "some value --flag"""
59+
let ``Tokens.ofString should not fail on missing end double quote``() =
60+
Tokens.ofString """cmd --arg "some value --flag"""
6161
=! [ token "cmd" 0 3
6262
token "--arg" 4 9
6363
sqtoken '"' "some value --flag" 11 28 ]
6464

6565
[<Fact>]
66-
let ``Token.ofString should treat single quotes as a single token and remove quotes``() =
67-
Token.ofString """cmd --arg 'some value' --flag"""
66+
let ``Tokens.ofString should treat single quotes as a single token and remove quotes``() =
67+
Tokens.ofString """cmd --arg 'some value' --flag"""
6868
=! [ token "cmd" 0 3
6969
token "--arg" 4 9
7070
qtoken '\'' "some value" 11 21
7171
token "--flag" 23 29]
7272

7373
[<Fact>]
74-
let ``Token.ofString should not fail on missing end single quote``() =
75-
Token.ofString """cmd --arg 'some value --flag"""
74+
let ``Tokens.ofString should not fail on missing end single quote``() =
75+
Tokens.ofString """cmd --arg 'some value --flag"""
7676
=! [ token "cmd" 0 3
7777
token "--arg" 4 9
7878
sqtoken '\'' "some value --flag" 11 28 ]
7979

8080
[<Fact>]
81-
let ``Token.ofString should accept single quotes in double quotes``() =
82-
Token.ofString """cmd --arg "some 'value" --flag"""
81+
let ``Tokens.ofString should accept single quotes in double quotes``() =
82+
Tokens.ofString """cmd --arg "some 'value" --flag"""
8383
=! [ token "cmd" 0 3
8484
token "--arg" 4 9
8585
qtoken '"' "some 'value" 11 22
8686
token "--flag" 24 30]
8787

8888
[<Fact>]
89-
let ``Token.ofString should accept double quotes in single quotes``() =
90-
Token.ofString """cmd --arg 'some "value' --flag"""
89+
let ``Tokens.ofString should accept double quotes in single quotes``() =
90+
Tokens.ofString """cmd --arg 'some "value' --flag"""
9191
=! [ token "cmd" 0 3
9292
token "--arg" 4 9
9393
qtoken '\'' """some "value""" 11 22
9494
token "--flag" 24 30 ]
9595

9696
[<Property>]
97-
let ``Token.ofCmdLine should behave as Token.ofString for single argument, Token.ofList otherwhise`` (args: string[]) =
97+
let ``Tokens.ofCmdLine should behave as Tokens.ofString for single argument, Tokens.ofList otherwhise`` (args: string[]) =
9898
match args with
99-
| [| x |] -> Token.ofCmdLine args = Token.ofString x
100-
| _ -> Token.ofCmdLine args = Token.ofList (Array.toList args)
99+
| [| x |] -> Tokens.ofCmdLine args = Tokens.ofString x
100+
| _ -> Tokens.ofCmdLine args = Tokens.ofList (Array.toList args)
101101

102102
[<Property>]
103-
let ``Token.ofString then Token.toString should give same result`` (NonNull (args: string)) =
103+
let ``Tokens.ofString then Token.toString should give same result`` (NonNull (args: string)) =
104104
let trimmed = args.TrimEnd()
105-
let result = trimmed |> Token.ofString |> Token.toString
105+
let result = trimmed |> Tokens.ofString |> Tokens.toString
106106
result =! trimmed
107107

108108

109109
[<Fact>]
110-
let ``Token.ofString then Token.toString should give same result with empty quotes `` () =
110+
let ``Tokens.ofString then Token.toString should give same result with empty quotes `` () =
111111
let trimmed = "\"\""
112-
let result = trimmed |> Token.ofString |> Token.toString
112+
let result = trimmed |> Tokens.ofString |> Tokens.toString
113113
result =! trimmed
114114

tests/Fargo.Test/Usage.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ open System.Threading.Tasks
1111
let (=!) (actual:'a) (expected: 'a) = Differ.Assert(expected, actual )
1212

1313
let outUsage (p,c) input =
14-
let _,_,usages = p (Token.ofString input)
14+
let _,_,usages = p (Tokens.ofString input)
1515
Testing.withStdout(fun _ -> printHelp usages)
1616

1717
let outRun p input =

0 commit comments

Comments
 (0)