Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions src/BlackFox.FsGetLine.Tests/Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,121 @@ let commonPrefixLengthTests =
Expect.isLessThan result maxLen "Result is less than max element length"
| None ->
()
]

[<Tests>]
let csharpDotHeuristicTests =
testList "csharpDotHeuristicTriggersCompletion" [
testCase "triggers completion after a '.' following an identifier" <| fun _ ->
let text = "foo."
Expect.isTrue (csharpDotHeuristicTriggersCompletion text text.Length) "identifier member access should complete"

testCase "does not trigger for a '.' inside a numeric literal" <| fun _ ->
let text = "1."
Expect.isFalse (csharpDotHeuristicTriggersCompletion text text.Length) "decimal point in a number literal"

testCase "does not trigger for a '.' in a multi-digit numeric literal" <| fun _ ->
let text = "123."
Expect.isFalse (csharpDotHeuristicTriggersCompletion text text.Length) "decimal point in a multi-digit number"

testCase "triggers when the digits are part of an underscore-prefixed identifier" <| fun _ ->
let text = "x_123."
Expect.isTrue (csharpDotHeuristicTriggersCompletion text text.Length) "identifier containing digits, not a numeric literal"

testCase "triggers when the digits follow a letter (identifier, not a numeric literal)" <| fun _ ->
let text = "x123."
Expect.isTrue (csharpDotHeuristicTriggersCompletion text text.Length) "identifier containing digits"
]

[<Tests>]
let completionStateTests =
let withCompletions items (cs: CompletionState.CompletionState) = { cs with Completions = items }

testList "CompletionState" [
testCase "create rejects a negative column" <| fun _ ->
Expect.throwsT<System.ArgumentException> (fun () -> CompletionState.create -1 0 10 5 |> ignore) "col < 0"

testCase "create rejects a negative row" <| fun _ ->
Expect.throwsT<System.ArgumentException> (fun () -> CompletionState.create 0 -1 10 5 |> ignore) "row < 0"

testCase "create rejects a width below one" <| fun _ ->
Expect.throwsT<System.ArgumentException> (fun () -> CompletionState.create 0 0 0 5 |> ignore) "width < 1"

testCase "create rejects a height below one" <| fun _ ->
Expect.throwsT<System.ArgumentException> (fun () -> CompletionState.create 0 0 10 0 |> ignore) "height < 1"

testCase "create accepts valid bounds" <| fun _ ->
let cs = CompletionState.create 0 0 10 5
Expect.equal cs.SelectedItem 0 "starts on the first item"
Expect.equal cs.TopItem 0 "starts scrolled to the top"

testCase "nextSelection advances the selection without scrolling while inside the window" <| fun _ ->
let cs = CompletionState.create 0 0 10 3 |> withCompletions [| "a"; "b"; "c"; "d"; "e" |]
match CompletionState.nextSelection cs with
| Some (selected, top) ->
Expect.equal selected 1 "moves to the next item"
Expect.equal top 0 "does not scroll yet, still inside the 3-row window"
| None -> failwith "expected Some"

testCase "nextSelection must not scroll while the new selection is still inside the window (642b064 fix)" <| fun _ ->
// Window height 3 currently showing items [1,2,3] (TopItem=1). Moving to item 2 is
// still inside that window. The pre-fix upstream formula
// (selected_item + top_item >= Height, i.e. 2+1=3>=3) would have scrolled here
// incorrectly; the fixed formula (selected_item - top_item >= Height, 2-1=1>=3) does not.
let cs =
{ CompletionState.create 0 0 10 3 with SelectedItem = 1; TopItem = 1 }
|> withCompletions [| "a"; "b"; "c"; "d"; "e" |]
match CompletionState.nextSelection cs with
| Some (selected, top) ->
Expect.equal selected 2 "moves to the next item"
Expect.equal top 1 "item 2 is still visible in the current window, must not scroll"
| None -> failwith "expected Some"

testCase "nextSelection scrolls the window by one once the selection passes the last visible row" <| fun _ ->
let cs =
{ CompletionState.create 0 0 10 3 with SelectedItem = 3; TopItem = 1 }
|> withCompletions [| "a"; "b"; "c"; "d"; "e" |]
match CompletionState.nextSelection cs with
| Some (selected, top) ->
Expect.equal selected 4 "moves to the next item"
Expect.equal top 2 "scrolls by exactly one row"
| None -> failwith "expected Some"

testCase "nextSelection returns None on the last item" <| fun _ ->
let cs =
{ CompletionState.create 0 0 10 3 with SelectedItem = 2; TopItem = 0 }
|> withCompletions [| "a"; "b"; "c" |]
Expect.isNone (CompletionState.nextSelection cs) "already on the last item"

testCase "previousSelection retreats the selection without scrolling while inside the window" <| fun _ ->
let cs =
{ CompletionState.create 0 0 10 3 with SelectedItem = 3; TopItem = 1 }
|> withCompletions [| "a"; "b"; "c"; "d"; "e" |]
match CompletionState.previousSelection cs with
| Some (selected, top) ->
Expect.equal selected 2 "moves to the previous item"
Expect.equal top 1 "item 2 is still within the current window, no scroll needed"
| None -> failwith "expected Some"

testCase "previousSelection scrolls up once the selection reaches the top row" <| fun _ ->
let cs =
{ CompletionState.create 0 0 10 3 with SelectedItem = 1; TopItem = 1 }
|> withCompletions [| "a"; "b"; "c"; "d"; "e" |]
match CompletionState.previousSelection cs with
| Some (selected, top) ->
Expect.equal selected 0 "moves to the first item"
Expect.equal top 0 "scrolls up to keep the selection visible"
| None -> failwith "expected Some"

testCase "previousSelection returns None on the first item" <| fun _ ->
let cs =
{ CompletionState.create 0 0 10 3 with SelectedItem = 0; TopItem = 0 }
|> withCompletions [| "a"; "b"; "c" |]
Expect.isNone (CompletionState.previousSelection cs) "already on the first item"

testCase "current returns the selected completion" <| fun _ ->
let cs =
{ CompletionState.create 0 0 10 3 with SelectedItem = 1 }
|> withCompletions [| "a"; "b"; "c" |]
Expect.equal (CompletionState.current cs) "b" "index 1 is 'b'"
]
30 changes: 15 additions & 15 deletions src/BlackFox.FsGetLine/ColoredString.fs
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ let private tryParseColor (s:string) =
| :? OverflowException -> NoColor

let private parseColorCodes (s:string) =
let split = s.Split(';')
let split = s.Split ';'

let foreground = if split.Length >= 1 then tryParseColor split.[0] else NoColor
let background = if split.Length >= 2 then tryParseColor split.[1] else NoColor

(foreground, background)
foreground, background

/// Support color markers in strings like ^[Red] or ^[Red;Blue] where the first color is
/// the foreground and the second the background.
Expand All @@ -39,27 +39,27 @@ type ColoredString(raw : string) =
s.Replace("^", "^^")

static member public Escape (s:string) =
new ColoredString(ColoredString.EscapeToString(s))
new ColoredString(ColoredString.EscapeToString s)

member private __.Fold (onChar: OnCharParsed<'st>) (onColor: OnColorParsed<'st>) (st:'st) =
member private __.Fold (onChar: OnCharParsed<'st>) (onColor: OnColorParsed<'st>) (st:'st) =
let foldFunc (st, escCount, content) c =
match escCount with
| 0 ->
match c with
| '^' -> (st, 1, "")
| _ -> (onChar st c, 0, "")
| '^' -> st, 1, ""
| _ -> onChar st c, 0, ""
| 1 ->
match c with
| '[' -> (st, 2, "")
| _ -> (onChar st c, 0, "")
| '[' -> st, 2, ""
| _ -> onChar st c, 0, ""
| 2 ->
match c with
| ']' -> (onColor st (parseColorCodes content), 0, "")
| _ -> (st, 2, content + (string)c)
| ']' -> onColor st (parseColorCodes content), 0, ""
| _ -> st, 2, content + string c
| _ ->
failwith("Impossible escape count")
failwith "Impossible escape count"

let (newSt, _, _) = raw |> Seq.fold foldFunc (st, 0, "")
let newSt, _, _ = raw |> Seq.fold foldFunc (st, 0, "")
newSt

member x.Length
Expand All @@ -86,14 +86,14 @@ type ColoredString(raw : string) =
let setColors foreground background =
setForeground foreground
setBackground background

inner setColors

setColors Reset Reset

member x.WriteToConsole () =
x.WriteCore (fun setColors ->
x.Fold (fun _ c -> Console.Write(c)) (fun _ (foreground, background) -> setColors foreground background) ()
x.Fold (fun _ c -> Console.Write c) (fun _ (foreground, background) -> setColors foreground background) ()
)

member x.WriteToConsoleFrom from =
Expand All @@ -115,4 +115,4 @@ let coloredWrite s =

let coloredWriteLine s =
coloredWrite s
Console.WriteLine()
Console.WriteLine ()
Loading