diff --git a/src/pastel/HumanReadableImplementation.re b/src/pastel/HumanReadableImplementation.re index 0333ceba..9f885594 100644 --- a/src/pastel/HumanReadableImplementation.re +++ b/src/pastel/HumanReadableImplementation.re @@ -14,6 +14,7 @@ module HumanReadableLexer = let length = HumanReadableLexer.length; let partition = HumanReadableLexer.partition; +let partition2 = HumanReadableLexer.partition2; let unformattedText = HumanReadableLexer.unformattedText; let makeDecorator = name => { diff --git a/src/pastel/Pastel.rei b/src/pastel/Pastel.rei index 16739462..51c1a556 100644 --- a/src/pastel/Pastel.rei +++ b/src/pastel/Pastel.rei @@ -69,6 +69,7 @@ type modifier = { let length: string => int; let partition: (int, string) => (string, string); +let partition2: (list(int), string) => list(string); let unformattedText: string => string; let modifier: modifier; diff --git a/src/pastel/PastelFactory.re b/src/pastel/PastelFactory.re index d48f21cf..ee10e3ec 100644 --- a/src/pastel/PastelFactory.re +++ b/src/pastel/PastelFactory.re @@ -119,6 +119,14 @@ module Make = (()) => { | Disabled => DisabledImplementation.partition(s) }; + let partition2 = s => { + switch (mode^) { + | HumanReadable => HumanReadableImplementation.partition2(s) + | Terminal => TerminalImplementation.partition2(s) + | Disabled => TerminalImplementation.partition2(s) + }; + }; + let unformattedText = s => switch (mode^) { | HumanReadable => HumanReadableImplementation.unformattedText(s) diff --git a/src/pastel/PastelLexer.re b/src/pastel/PastelLexer.re index c1ba9f71..04c2308b 100644 --- a/src/pastel/PastelLexer.re +++ b/src/pastel/PastelLexer.re @@ -90,6 +90,112 @@ module Make = (M: { ) |> String.concat(""); + type partitionAcc = { + activeIndex: int, + remainingIndices: list(int), + nonTextTokensSeen: list(token), + parsedText: list(list(token)), + charsSeen: int, + activeSection: list(token), + queue: list(token), + }; + + let partition2 = (indices, s) => { + let tokens = tokenize(s); + + switch (indices) { + | [] => [s] + | [h, ...t] => + let initialState = { + activeIndex: h, + remainingIndices: t, + nonTextTokensSeen: [], + parsedText: [], + charsSeen: 0, + activeSection: [], + queue: tokens, + }; + + let step = state => + switch (state.queue) { + | [] => state + | [token, ...remainingQueue] => + switch (token) { + | Starts(_) + | Stops(_) => { + ...state, + nonTextTokensSeen: [token, ...state.nonTextTokensSeen], + parsedText: + List.map(tokens => [token, ...tokens], state.parsedText), + activeSection: [token, ... state.activeSection], + queue: remainingQueue, + } + | Text(text) => + if (state.charsSeen < state.activeIndex) { + let textLength = String.length(text); + if (state.charsSeen + textLength < state.activeIndex) { + { + ...state, + charsSeen: state.charsSeen + textLength, + activeSection: [token, ...state.activeSection], + queue: remainingQueue, + }; + } else { + let numToTake = state.activeIndex - state.charsSeen; + let remaining = String.length(text) - numToTake; + let remainingText = String.sub(text, numToTake, remaining); + let firstPart = Text(String.sub(text, 0, numToTake)); + + switch (state.remainingIndices) { + | [] => { + ...state, + charsSeen: state.charsSeen + textLength, + activeSection: [ + Text(remainingText), + ...state.nonTextTokensSeen, + ], + parsedText: [ + [firstPart, ...state.activeSection], + ...state.parsedText, + ], + queue: remainingQueue, + } + | [h, ...t] => { + ...state, + charsSeen: state.charsSeen + numToTake, + activeSection: state.nonTextTokensSeen, + parsedText: [ + [firstPart, ...state.activeSection], + ...state.parsedText, + ], + queue: [Text(remainingText), ...remainingQueue], + activeIndex: h, + remainingIndices: t, + } + }; + }; + } else { + { + ...state, + activeSection: [token, ...state.activeSection], + queue: remainingQueue, + }; + } + } + }; + + let state = ref(initialState); + while (state^.queue !== []) { + state := step(state^); + }; + let parsedText = [state^.activeSection, ...state^.parsedText] + List.map( + tokens => tokensToStr(List.rev(tokens)), + List.rev(parsedText), + ); + }; + }; + /** everything before index is one string, everything else is the other */ let partition = (index, s) => { let tokens = tokenize(s); diff --git a/src/pastel/PastelSig.re b/src/pastel/PastelSig.re index 8c99122a..de175f6d 100644 --- a/src/pastel/PastelSig.re +++ b/src/pastel/PastelSig.re @@ -14,6 +14,7 @@ module type PastelSig = { let useMode: (mode, unit => 'a) => 'a; let length: string => int; let partition: (int, string) => (string, string); + let partition2: (list(int), string) => list(string); let unformattedText: string => string; let modifier: modifier; diff --git a/src/pastel/TerminalImplementation.re b/src/pastel/TerminalImplementation.re index 88db7f53..116fde50 100644 --- a/src/pastel/TerminalImplementation.re +++ b/src/pastel/TerminalImplementation.re @@ -23,6 +23,7 @@ module TerminalLexer = let length = TerminalLexer.length; let partition = TerminalLexer.partition; +let partition2 = TerminalLexer.partition2; let unformattedText = TerminalLexer.unformattedText; let rec reduceTokens = (style: Ansi.style, str, tokens) => diff --git a/tests/__snapshots__/Pastel_Pastel_Human_Readable_mode_Pastel_partition.9f56c617.0.snapshot b/tests/__snapshots__/Pastel_Pastel_Human_Readable_mode_Pastel_partition.9f56c617.0.snapshot new file mode 100644 index 00000000..030326e5 --- /dev/null +++ b/tests/__snapshots__/Pastel_Pastel_Human_Readable_mode_Pastel_partition.9f56c617.0.snapshot @@ -0,0 +1,6 @@ +Pastel › Pastel Human Readable mode › Pastel.partition2 › complicated nested formatting +ooh +ello + wor +ld!u +npasteled o_O diff --git a/tests/__snapshots__/Pastel_Pastel_disabled_mode_Pastel_partition.3369e703.0.snapshot b/tests/__snapshots__/Pastel_Pastel_disabled_mode_Pastel_partition.3369e703.0.snapshot new file mode 100644 index 00000000..de9e68ee --- /dev/null +++ b/tests/__snapshots__/Pastel_Pastel_disabled_mode_Pastel_partition.3369e703.0.snapshot @@ -0,0 +1,6 @@ +Pastel › Pastel disabled mode › Pastel.partition2 › complicated nested formatting +ooh +ello + wor +ld!u +npasteled o_O diff --git a/tests/__snapshots__/Pastel_Pastel_terminal_mode_Pastel_partition.f482f7df.0.snapshot b/tests/__snapshots__/Pastel_Pastel_terminal_mode_Pastel_partition.f482f7df.0.snapshot new file mode 100644 index 00000000..8ed2ae4c --- /dev/null +++ b/tests/__snapshots__/Pastel_Pastel_terminal_mode_Pastel_partition.f482f7df.0.snapshot @@ -0,0 +1,6 @@ +Pastel › Pastel terminal mode › Pastel.partition2 › complicated nested formatting +\027[2moo\027[22m\027[2m\027[32mh\027[39m\027[22m\027[2m\027[22m +\027[2m\027[22m\027[2m\027[32mello\027[39m\027[22m\027[2m\027[22m +\027[2m\027[22m\027[2m\027[32m wor\027[39m\027[22m\027[2m\027[22m +\027[2m\027[22m\027[2m\027[32mld!\027[39m\027[22m\027[2mu\027[22m +\027[2m\027[22m\027[2m\027[32m\027[39m\027[22m\027[2mnpasteled o_O\027[22m diff --git a/tests/__tests__/pastel/Pastel_test.re b/tests/__tests__/pastel/Pastel_test.re index 13618b37..6b1f3657 100644 --- a/tests/__tests__/pastel/Pastel_test.re +++ b/tests/__tests__/pastel/Pastel_test.re @@ -116,20 +116,24 @@ describe("Pastel", ({describe, test}) => { PastelWithSpecifiedMode.partition(45, input); expect.string(part1).toEqual(input); - expect.string(PastelWithSpecifiedMode.unformattedText(part2)).toBeEmpty(); + expect.string(PastelWithSpecifiedMode.unformattedText(part2)). + toBeEmpty(); }); - test( - "index equal to length should return (s, '')", ({expect}) => { + test("index equal to length should return (s, '')", ({expect}) => { let input = "Hello world" ; (); let (part1, part2) = - PastelWithSpecifiedMode.partition(PastelWithSpecifiedMode.length(input), input); + PastelWithSpecifiedMode.partition( + PastelWithSpecifiedMode.length(input), + input, + ); expect.string(part1).toEqual(input); - expect.string(PastelWithSpecifiedMode.unformattedText(part2)).toBeEmpty(); + expect.string(PastelWithSpecifiedMode.unformattedText(part2)). + toBeEmpty(); }); test("negative index should return ('', s)", ({expect}) => { let input = @@ -140,7 +144,8 @@ describe("Pastel", ({describe, test}) => { let (part1, part2) = PastelWithSpecifiedMode.partition(-1, input); - expect.string(PastelWithSpecifiedMode.unformattedText(part1)).toBeEmpty(); + expect.string(PastelWithSpecifiedMode.unformattedText(part1)). + toBeEmpty(); expect.string(part2).toEqual(input); }); test("index 0 should return ('', s)", ({expect}) => { @@ -152,10 +157,40 @@ describe("Pastel", ({describe, test}) => { let (part1, part2) = PastelWithSpecifiedMode.partition(0, input); - expect.string(PastelWithSpecifiedMode.unformattedText(part1)).toBeEmpty(); + expect.string(PastelWithSpecifiedMode.unformattedText(part1)). + toBeEmpty(); expect.string(part2).toEqual(input); }); }); + describe("Pastel.partition2", ({test}) => { + test("unformatted text", ({expect}) => { + let result = + PastelWithSpecifiedMode.partition2( + [4, 6, 9], + "hello world!", + ); + expect.lines(result).toEqualLines([ + "hell", + "o ", + "wor", + "ld!", + ]); + }); + test("complicated nested formatting", ({expect}) => { + let input = + + "oo" + + "hello world!" + + "unpasteled o_O" + ; + + let result = + PastelWithSpecifiedMode.partition2([3, 7, 11, 15], input); + expect.lines(result).toMatchSnapshot(); + }); + }); describe("Pastel.length", ({test}) => { test("Length with no colors", ({expect}) => { expect.int(length("")).toBe(0);