@@ -36,30 +36,9 @@ public function lines(): \Generator
3636 }
3737
3838 /** @throws \OutOfBoundsException if the offset lies outside the source */
39- public function lineAtOffset (int $ offset ): Line
39+ public function lineNumberAtOffset (int $ offset ): int
4040 {
41- $ sourceLength = strlen ($ this ->content );
42- if ($ offset < 0 || $ offset > $ sourceLength ) {
43- throw new \OutOfBoundsException (
44- sprintf ('Source offset %d is outside the valid range 0..%d. ' , $ offset , $ sourceLength ),
45- );
46- }
47-
48- $ lineBeginOffsets = $ this ->lineBeginOffsets ();
49- $ low = 0 ;
50- $ high = count ($ lineBeginOffsets ) - 1 ;
51-
52- while ($ low < $ high ) {
53- $ middle = intdiv ($ low + $ high + 1 , 2 );
54-
55- if ($ lineBeginOffsets [$ middle ] <= $ offset ) {
56- $ low = $ middle ;
57- } else {
58- $ high = $ middle - 1 ;
59- }
60- }
61-
62- return $ this ->createLineAtOffset ($ low + 1 , $ lineBeginOffsets [$ low ]);
41+ return $ this ->lineIndexAtOffset ($ offset , $ this ->lineBeginOffsets ()) + 1 ;
6342 }
6443
6544 public function withContent (string $ content ): self
@@ -77,18 +56,58 @@ private function lineBeginOffsets(): array
7756 }
7857
7958 $ lineBeginOffsets = [0 ];
59+ $ sourceLength = strlen ($ this ->content );
60+ $ lineBeginOffset = 0 ;
61+
62+ while ($ lineBeginOffset < $ sourceLength ) {
63+ $ lineLength = strcspn ($ this ->content , "\r\n" , $ lineBeginOffset );
64+ $ lineEndingOffset = $ lineBeginOffset + $ lineLength ;
8065
81- foreach ($ this ->lines () as $ line ) {
82- if ($ line ->number === 1 ) {
83- continue ;
66+ if ($ lineEndingOffset === $ sourceLength ) {
67+ break ;
8468 }
8569
86- $ lineBeginOffsets [] = $ line ->beginOffset ;
70+ $ lineBeginOffset = $ lineEndingOffset + (
71+ $ this ->content [$ lineEndingOffset ] === "\r"
72+ && ($ this ->content [$ lineEndingOffset + 1 ] ?? null ) === "\n"
73+ ? 2
74+ : 1
75+ );
76+ $ lineBeginOffsets [] = $ lineBeginOffset ;
8777 }
8878
8979 return $ this ->lineBeginOffsets = $ lineBeginOffsets ;
9080 }
9181
82+ /**
83+ * @param non-empty-list<int> $lineBeginOffsets
84+ * @throws \OutOfBoundsException if the offset lies outside the source
85+ */
86+ private function lineIndexAtOffset (int $ offset , array $ lineBeginOffsets ): int
87+ {
88+ $ sourceLength = strlen ($ this ->content );
89+ if ($ offset < 0 || $ offset > $ sourceLength ) {
90+ throw new \OutOfBoundsException (
91+ sprintf ('Source offset %d is outside the valid range 0..%d. ' , $ offset , $ sourceLength ),
92+ );
93+ }
94+
95+ $ low = 0 ;
96+ $ high = count ($ lineBeginOffsets ) - 1 ;
97+
98+ while ($ low < $ high ) {
99+ $ middle = intdiv ($ low + $ high + 1 , 2 );
100+
101+ if ($ lineBeginOffsets [$ middle ] <= $ offset ) {
102+ $ low = $ middle ;
103+ } else {
104+ $ high = $ middle - 1 ;
105+ }
106+ }
107+
108+ return $ low ;
109+ }
110+
92111 private function createLineAtOffset (int $ lineNumber , int $ lineBeginOffset ): Line
93112 {
94113 $ sourceLength = strlen ($ this ->content );
0 commit comments