@@ -97,7 +97,7 @@ pub fn find_block_boundaries(
9797
9898 let ( language, block_start, block_end) = match extension {
9999 "py" => find_python_block ( entries, anchor_index) ?,
100- "verse" => find_python_block ( entries, anchor_index) ?,
100+ "verse" => find_verse_block ( entries, anchor_index) ?,
101101 "rb" => find_ruby_block ( entries, anchor_index) ?,
102102 "rs" | "js" | "ts" | "tsx" | "jsx" | "go" | "java" | "c" | "cpp" | "h" | "hpp" | "cs"
103103 | "kt" | "kts" | "swift" | "scala" | "dart" | "zig" | "m" | "mm" => {
@@ -300,6 +300,77 @@ fn find_indent_block(
300300 Ok ( ( start, end) )
301301}
302302
303+ fn find_verse_block (
304+ entries : & [ crate :: document:: LineEntry ] ,
305+ anchor_index : usize ,
306+ ) -> Result < ( Option < String > , usize , usize ) , HashlineError > {
307+ // Verse combines indented class/method bodies (`base_x := class():` /
308+ // `OnBegin():void=`) with brace blocks (`using { /Verse... }`,
309+ // `if (X) { ... }`). Try indent first (most blocks are class bodies),
310+ // then brace-balanced for anchor lines that live inside `{ }`.
311+ if let Ok ( ( s, e) ) = find_verse_indent_block ( entries, anchor_index) {
312+ return Ok ( ( Some ( "Verse" . into ( ) ) , s, e) ) ;
313+ }
314+ if let Ok ( ( s, e) ) = find_brace_block ( entries, anchor_index, "verse" ) {
315+ return Ok ( ( Some ( "Verse" . into ( ) ) , s, e) ) ;
316+ }
317+ // Last resort: return the whole file as a single block (top-level anchor).
318+ Ok ( ( Some ( "Verse" . into ( ) ) , 0 , entries. len ( ) . saturating_sub ( 1 ) ) )
319+ }
320+
321+ fn find_verse_indent_block (
322+ entries : & [ crate :: document:: LineEntry ] ,
323+ anchor_index : usize ,
324+ ) -> Result < ( usize , usize ) , HashlineError > {
325+ let anchor_indent = leading_whitespace ( & entries[ anchor_index] . content ) ;
326+
327+ // Walk backwards for the first non-empty line with strictly less
328+ // indentation. If we never find one (anchor is at top of file), look
329+ // forward instead: the first non-empty line with indent <= anchor_indent
330+ // marks the block end, and the block extends from anchor_index..end.
331+ let mut start: Option < usize > = None ;
332+ for i in ( 0 ..anchor_index) . rev ( ) {
333+ if entries[ i] . content . trim ( ) . is_empty ( ) {
334+ continue ;
335+ }
336+ let indent = leading_whitespace ( & entries[ i] . content ) ;
337+ if indent < anchor_indent {
338+ start = Some ( i) ;
339+ break ;
340+ }
341+ }
342+ let ( start, start_indent) = if let Some ( s) = start {
343+ ( s, leading_whitespace ( & entries[ s] . content ) )
344+ } else {
345+ // Top-of-file anchor: block extends back to line 0.
346+ ( 0 , leading_whitespace ( & entries[ 0 ] . content ) )
347+ } ;
348+
349+ let mut end = entries. len ( ) . saturating_sub ( 1 ) ;
350+ let mut found_end = false ;
351+ for i in ( start + 1 ) ..entries. len ( ) {
352+ let entry = & entries[ i] ;
353+ let trimmed = entry. content . trim ( ) ;
354+ if trimmed. is_empty ( ) {
355+ continue ;
356+ }
357+ // Verse treats lines beginning with `#` as a comment-like marker,
358+ // matching the Python rule. There is no native block-comment in
359+ // Verse, so we only need to skip `#` rows.
360+ if trimmed. starts_with ( '#' ) {
361+ continue ;
362+ }
363+ let indent = leading_whitespace ( & entry. content ) ;
364+ if indent <= start_indent {
365+ end = i. saturating_sub ( 1 ) ;
366+ found_end = true ;
367+ break ;
368+ }
369+ }
370+ let _ = found_end;
371+ Ok ( ( start, end) )
372+ }
373+
303374fn find_python_block (
304375 entries : & [ crate :: document:: LineEntry ] ,
305376 anchor_index : usize ,
@@ -493,6 +564,32 @@ mod tests {
493564 assert_eq ! ( payload. block_lines[ 2 ] . content, "}" ) ;
494565 }
495566
567+ #[ test]
568+ fn test_verse_top_level_using ( ) {
569+ let content = "using { /Verse.org/Simulation }\n using { /Fortnite.com/Devices }\n \n base_x := class():\n x:int = 0\n " ;
570+ let ( fc, entries) = make_fc ( content, "test.verse" ) ;
571+ // Anchor on the top-level `using` should not fail.
572+ let payload = find_block_payload ( & fc, & entries, & anchor_for ( 1 , & entries) ) . unwrap ( ) ;
573+ assert_eq ! ( payload. language. as_deref( ) , Some ( "Verse" ) ) ;
574+ assert ! ( !payload. block_lines. is_empty( ) ) ;
575+ }
576+
577+ #[ test]
578+ fn test_verse_class_body ( ) {
579+ let content = "using { /Verse.org/Simulation }\n \n signal_device := class(creative_device):\n var Count:int = 0\n OnBegin<override>()<suspends>:void=\n Count += 1\n Print(\" ok\" )\n " ;
580+ let ( fc, entries) = make_fc ( content, "test.verse" ) ;
581+ // Anchor inside the class body (the var Count line) should resolve
582+ // to the enclosing class block.
583+ let payload = find_block_payload ( & fc, & entries, & anchor_for ( 4 , & entries) ) . unwrap ( ) ;
584+ assert_eq ! ( payload. language. as_deref( ) , Some ( "Verse" ) ) ;
585+ assert ! (
586+ payload
587+ . block_lines
588+ . iter( )
589+ . any( |l| l. content. starts_with( "signal_device" ) )
590+ ) ;
591+ }
592+
496593 #[ test]
497594 fn test_go_block ( ) {
498595 let content = "func main() {\n \t if true {\n \t \t fmt.Println(\" ok\" )\n \t }\n }\n " ;
0 commit comments