1- use std:: cell:: OnceCell ;
2- use std:: ops:: Range ;
31use std:: path:: { Path , PathBuf } ;
42
5- /// A signature file's content with byte position <-> line/column conversion,
6- /// mirroring `RBS::Buffer`.
7- ///
8- /// Positions and columns are byte offsets, unlike Ruby's character offsets;
9- /// they agree on ASCII-only content. Use the parser's `RBSLocationRange` when
10- /// character offsets are needed.
3+ /// A signature file's content, mirroring `RBS::Buffer`.
114#[ derive( Debug , Clone ) ]
125pub struct Buffer {
136 name : PathBuf ,
147 content : String ,
15- line_ranges : OnceCell < Vec < Range < usize > > > ,
168}
179
1810impl Buffer {
1911 pub fn new ( name : PathBuf , content : String ) -> Self {
20- Self {
21- name,
22- content,
23- line_ranges : OnceCell :: new ( ) ,
24- }
12+ Self { name, content }
2513 }
2614
2715 pub fn name ( & self ) -> & Path {
@@ -31,154 +19,4 @@ impl Buffer {
3119 pub fn content ( & self ) -> & str {
3220 & self . content
3321 }
34-
35- fn line_ranges ( & self ) -> & [ Range < usize > ] {
36- self . line_ranges
37- . get_or_init ( || compute_line_ranges ( & self . content ) )
38- }
39-
40- pub fn line_count ( & self ) -> usize {
41- self . line_ranges ( ) . len ( )
42- }
43-
44- /// Returns the 1-origin `line` without its line terminator.
45- pub fn line ( & self , line : usize ) -> Option < & str > {
46- let range = self . line_ranges ( ) . get ( line. checked_sub ( 1 ) ?) ?;
47- Some ( & self . content [ range. clone ( ) ] )
48- }
49-
50- /// A position past the end of the content maps to `(line_count + 1, 0)`,
51- /// same as the Ruby implementation.
52- pub fn pos_to_loc ( & self , pos : usize ) -> ( usize , usize ) {
53- let ranges = self . line_ranges ( ) ;
54- let index = ranges. partition_point ( |range| range. end < pos) ;
55- match ranges. get ( index) {
56- Some ( range) => ( index + 1 , pos. saturating_sub ( range. start ) ) ,
57- None => ( ranges. len ( ) + 1 , 0 ) ,
58- }
59- }
60-
61- pub fn loc_to_pos ( & self , line : usize , column : usize ) -> usize {
62- let ranges = self . line_ranges ( ) ;
63- let range = match line. checked_sub ( 1 ) {
64- None => ranges. last ( ) ,
65- Some ( index) => ranges. get ( index) ,
66- } ;
67- match range {
68- Some ( range) => range. start + column,
69- None => self . last_position ( ) ,
70- }
71- }
72-
73- pub fn last_position ( & self ) -> usize {
74- self . line_ranges ( ) . last ( ) . map_or ( 0 , |range| range. end )
75- }
76- }
77-
78- fn compute_line_ranges ( content : & str ) -> Vec < Range < usize > > {
79- let mut ranges = Vec :: new ( ) ;
80- let mut offset = 0 ;
81-
82- for line in content. split_inclusive ( '\n' ) {
83- let without_terminator = match line. strip_suffix ( '\n' ) {
84- Some ( l) => l. strip_suffix ( '\r' ) . unwrap_or ( l) ,
85- None => line. strip_suffix ( '\r' ) . unwrap_or ( line) ,
86- } ;
87- ranges. push ( offset..offset + without_terminator. len ( ) ) ;
88- offset += line. len ( ) ;
89- }
90-
91- if content. is_empty ( ) || content. ends_with ( '\n' ) {
92- ranges. push ( offset..offset) ;
93- }
94-
95- ranges
96- }
97-
98- #[ cfg( test) ]
99- mod tests {
100- use super :: * ;
101-
102- fn buffer ( content : & str ) -> Buffer {
103- Buffer :: new ( PathBuf :: from ( "a.rbs" ) , content. to_string ( ) )
104- }
105-
106- #[ test]
107- fn lines_of_content_with_trailing_newline ( ) {
108- let buffer = buffer ( "123\n abc\n " ) ;
109- assert_eq ! ( buffer. line_count( ) , 3 ) ;
110- assert_eq ! ( buffer. line( 1 ) , Some ( "123" ) ) ;
111- assert_eq ! ( buffer. line( 2 ) , Some ( "abc" ) ) ;
112- assert_eq ! ( buffer. line( 3 ) , Some ( "" ) ) ;
113- assert_eq ! ( buffer. line( 4 ) , None ) ;
114- assert_eq ! ( buffer. line( 0 ) , None ) ;
115- }
116-
117- #[ test]
118- fn lines_of_content_without_trailing_newline ( ) {
119- let buffer = buffer ( "123\n abc" ) ;
120- assert_eq ! ( buffer. line_count( ) , 2 ) ;
121- assert_eq ! ( buffer. line( 2 ) , Some ( "abc" ) ) ;
122- assert_eq ! ( buffer. last_position( ) , 7 ) ;
123- }
124-
125- #[ test]
126- fn empty_content_has_one_empty_line ( ) {
127- let buffer = buffer ( "" ) ;
128- assert_eq ! ( buffer. line_count( ) , 1 ) ;
129- assert_eq ! ( buffer. line( 1 ) , Some ( "" ) ) ;
130- assert_eq ! ( buffer. last_position( ) , 0 ) ;
131- assert_eq ! ( buffer. pos_to_loc( 0 ) , ( 1 , 0 ) ) ;
132- }
133-
134- #[ test]
135- fn crlf_is_excluded_from_line_ranges ( ) {
136- let buffer = buffer ( "abc\r \n def\r \n " ) ;
137- assert_eq ! ( buffer. line( 1 ) , Some ( "abc" ) ) ;
138- assert_eq ! ( buffer. line( 2 ) , Some ( "def" ) ) ;
139- assert_eq ! ( buffer. pos_to_loc( 5 ) , ( 2 , 0 ) ) ;
140- }
141-
142- #[ test]
143- fn pos_to_loc_matches_ruby_buffer_for_ascii ( ) {
144- let buffer = buffer ( "123\n abc\n " ) ;
145- assert_eq ! ( buffer. pos_to_loc( 0 ) , ( 1 , 0 ) ) ;
146- assert_eq ! ( buffer. pos_to_loc( 3 ) , ( 1 , 3 ) ) ;
147- assert_eq ! ( buffer. pos_to_loc( 4 ) , ( 2 , 0 ) ) ;
148- assert_eq ! ( buffer. pos_to_loc( 7 ) , ( 2 , 3 ) ) ;
149- assert_eq ! ( buffer. pos_to_loc( 8 ) , ( 3 , 0 ) ) ;
150- assert_eq ! ( buffer. pos_to_loc( 9 ) , ( 4 , 0 ) ) ;
151- }
152-
153- #[ test]
154- fn loc_to_pos_matches_ruby_buffer_for_ascii ( ) {
155- let buffer = buffer ( "123\n abc\n " ) ;
156- assert_eq ! ( buffer. loc_to_pos( 1 , 0 ) , 0 ) ;
157- assert_eq ! ( buffer. loc_to_pos( 2 , 3 ) , 7 ) ;
158- assert_eq ! ( buffer. loc_to_pos( 3 , 0 ) , 8 ) ;
159- assert_eq ! ( buffer. loc_to_pos( 10 , 5 ) , 8 ) ;
160- assert_eq ! ( buffer. last_position( ) , 8 ) ;
161- }
162-
163- #[ test]
164- fn trailing_carriage_return_without_newline_is_chomped ( ) {
165- let buffer = buffer ( "abc\n def\r " ) ;
166- assert_eq ! ( buffer. line( 2 ) , Some ( "def" ) ) ;
167- assert_eq ! ( buffer. last_position( ) , 7 ) ;
168- }
169-
170- #[ test]
171- fn line_zero_maps_to_the_last_line ( ) {
172- let buffer = buffer ( "123\n abc" ) ;
173- assert_eq ! ( buffer. loc_to_pos( 0 , 2 ) , 6 ) ;
174- }
175-
176- #[ test]
177- fn the_line_table_is_built_only_on_demand ( ) {
178- let buffer = buffer ( "123\n abc\n " ) ;
179- assert ! ( buffer. line_ranges. get( ) . is_none( ) ) ;
180-
181- buffer. pos_to_loc ( 4 ) ;
182- assert ! ( buffer. line_ranges. get( ) . is_some( ) ) ;
183- }
18422}
0 commit comments