@@ -46,16 +46,38 @@ impl DelimiterLineStartSnapshot {
4646 pub ( crate ) fn frame_state ( & self ) -> DelimiterFrameState {
4747 DelimiterFrameState {
4848 stack : self . visible_frames . clone ( ) ,
49+ close_generations_by_resulting_depth : None ,
4950 }
5051 }
5152}
5253
54+ #[ derive( Clone , Copy , Debug , Default , Eq , PartialEq ) ]
55+ pub ( crate ) struct DelimiterBoundary {
56+ depth : usize ,
57+ close_generation_below : u64 ,
58+ }
59+
60+ impl DelimiterBoundary {
61+ #[ inline]
62+ pub ( crate ) fn depth ( self ) -> usize {
63+ self . depth
64+ }
65+ }
66+
5367#[ derive( Clone , Debug , Default , Eq , PartialEq ) ]
5468pub ( crate ) struct DelimiterFrameState {
5569 stack : Vec < DelimiterFrameKind > ,
70+ close_generations_by_resulting_depth : Option < Vec < u64 > > ,
5671}
5772
5873impl DelimiterFrameState {
74+ pub ( crate ) fn with_boundary_tracking ( ) -> Self {
75+ Self {
76+ stack : Vec :: new ( ) ,
77+ close_generations_by_resulting_depth : Some ( Vec :: new ( ) ) ,
78+ }
79+ }
80+
5981 #[ inline]
6082 pub ( crate ) fn depth ( & self ) -> usize {
6183 self . stack . len ( )
@@ -69,6 +91,36 @@ impl DelimiterFrameState {
6991 self . stack . last ( ) == Some ( & DelimiterFrameKind :: Bracket )
7092 }
7193
94+ pub ( crate ) fn boundary ( & self ) -> DelimiterBoundary {
95+ let depth = self . depth ( ) ;
96+ let close_generation_below = depth
97+ . checked_sub ( 1 )
98+ . and_then ( |resulting_depth| {
99+ self . close_generations_by_resulting_depth
100+ . as_ref ( )
101+ . and_then ( |generations| generations. get ( resulting_depth) )
102+ } )
103+ . copied ( )
104+ . unwrap_or ( 0 ) ;
105+
106+ DelimiterBoundary {
107+ depth,
108+ close_generation_below,
109+ }
110+ }
111+
112+ pub ( crate ) fn has_closed_below ( & self , boundary : DelimiterBoundary ) -> bool {
113+ let Some ( resulting_depth) = boundary. depth . checked_sub ( 1 ) else {
114+ return false ;
115+ } ;
116+ self . close_generations_by_resulting_depth
117+ . as_ref ( )
118+ . and_then ( |generations| generations. get ( resulting_depth) )
119+ . copied ( )
120+ . unwrap_or ( 0 )
121+ != boundary. close_generation_below
122+ }
123+
72124 pub ( crate ) fn apply_token ( & mut self , token : & SqlToken ) {
73125 let SqlToken :: Symbol ( symbol) = token else {
74126 return ;
@@ -118,6 +170,7 @@ impl DelimiterFrameState {
118170 . is_some_and ( |top| top == close_kind)
119171 {
120172 let _ = self . stack . pop ( ) ;
173+ self . record_successful_close ( ) ;
121174 }
122175 }
123176 }
@@ -140,6 +193,7 @@ impl DelimiterFrameState {
140193 . is_some_and ( |top| top. can_be_closed_by ( close_kind) )
141194 {
142195 let _ = self . stack . pop ( ) ;
196+ self . record_successful_close ( ) ;
143197 if self . stack . len ( ) < baseline_depth {
144198 return true ;
145199 }
@@ -148,6 +202,21 @@ impl DelimiterFrameState {
148202
149203 false
150204 }
205+
206+ fn record_successful_close ( & mut self ) {
207+ let Some ( close_generations_by_resulting_depth) =
208+ self . close_generations_by_resulting_depth . as_mut ( )
209+ else {
210+ return ;
211+ } ;
212+ let resulting_depth = self . stack . len ( ) ;
213+ if close_generations_by_resulting_depth. len ( ) <= resulting_depth {
214+ close_generations_by_resulting_depth. resize ( resulting_depth. saturating_add ( 1 ) , 0 ) ;
215+ }
216+ if let Some ( generation) = close_generations_by_resulting_depth. get_mut ( resulting_depth) {
217+ * generation = generation. wrapping_add ( 1 ) ;
218+ }
219+ }
151220}
152221
153222pub ( crate ) fn line_start_snapshot_before_token (
@@ -191,6 +260,7 @@ pub(crate) fn line_closes_delimiter_frame_below_snapshot_before_token(
191260mod tests {
192261 use super :: {
193262 line_closes_delimiter_frame_below_snapshot_before_token, line_start_snapshot_before_token,
263+ DelimiterFrameState ,
194264 } ;
195265 use crate :: ui:: sql_editor:: query_text:: tokenize_sql;
196266 use crate :: ui:: sql_editor:: SqlToken ;
@@ -242,4 +312,32 @@ mod tests {
242312 & tokens, 0 , comma_idx, & snapshot,
243313 ) ) ;
244314 }
315+
316+ #[ test]
317+ fn boundary_detects_close_even_when_the_same_depth_reopens ( ) {
318+ let mut state = DelimiterFrameState :: with_boundary_tracking ( ) ;
319+ state. apply_token ( & SqlToken :: Symbol ( "(" . to_string ( ) ) ) ;
320+ let boundary = state. boundary ( ) ;
321+
322+ state. apply_token ( & SqlToken :: Symbol ( ")" . to_string ( ) ) ) ;
323+ state. apply_token ( & SqlToken :: Symbol ( "(" . to_string ( ) ) ) ;
324+
325+ assert_eq ! ( state. depth( ) , boundary. depth( ) ) ;
326+ assert ! ( state. has_closed_below( boundary) ) ;
327+ }
328+
329+ #[ test]
330+ fn boundary_ignores_inner_and_mismatched_closes ( ) {
331+ let mut state = DelimiterFrameState :: with_boundary_tracking ( ) ;
332+ state. apply_token ( & SqlToken :: Symbol ( "(" . to_string ( ) ) ) ;
333+ let boundary = state. boundary ( ) ;
334+
335+ state. apply_token ( & SqlToken :: Symbol ( "[" . to_string ( ) ) ) ;
336+ state. apply_token ( & SqlToken :: Symbol ( ")" . to_string ( ) ) ) ;
337+ assert ! ( !state. has_closed_below( boundary) ) ;
338+
339+ state. apply_token ( & SqlToken :: Symbol ( "]" . to_string ( ) ) ) ;
340+ assert ! ( !state. has_closed_below( boundary) ) ;
341+ assert_eq ! ( state. depth( ) , boundary. depth( ) ) ;
342+ }
245343}
0 commit comments