22use miette:: SourceSpan ;
33use std:: fmt:: Display ;
44
5- use crate :: { FormatConfig , KdlError , KdlNode , KdlValue } ;
5+ use crate :: { FormatConfig , KdlError , KdlNode , KdlNodeFormat , KdlValue } ;
66
77/// Represents a KDL
88/// [`Document`](https://github.com/kdl-org/kdl/blob/main/SPEC.md#document).
@@ -344,15 +344,20 @@ impl KdlDocument {
344344 pub fn parse ( s : & str ) -> Result < Self , KdlError > {
345345 #[ cfg( not( feature = "v1-fallback" ) ) ]
346346 {
347- crate :: v2_parser :: try_parse ( crate :: v2_parser :: document , s)
347+ KdlDocument :: parse_v2 ( s)
348348 }
349349 #[ cfg( feature = "v1-fallback" ) ]
350350 {
351- crate :: v2_parser:: try_parse ( crate :: v2_parser:: document, s)
352- . or_else ( |e| KdlDocument :: parse_v1 ( s) . map_err ( |_| e) )
351+ KdlDocument :: parse_v2 ( s) . or_else ( |e| KdlDocument :: parse_v1 ( s) . map_err ( |_| e) )
353352 }
354353 }
355354
355+ /// Parses a KDL v2 string into a document.
356+ #[ cfg( feature = "v1" ) ]
357+ pub fn parse_v2 ( s : & str ) -> Result < Self , KdlError > {
358+ crate :: v2_parser:: try_parse ( crate :: v2_parser:: document, s)
359+ }
360+
356361 /// Parses a KDL v1 string into a document.
357362 #[ cfg( feature = "v1" ) ]
358363 pub fn parse_v1 ( s : & str ) -> Result < Self , KdlError > {
@@ -365,9 +370,74 @@ impl KdlDocument {
365370 #[ cfg( feature = "v1" ) ]
366371 pub fn v1_to_v2 ( s : & str ) -> Result < String , KdlError > {
367372 let mut doc = KdlDocument :: parse_v1 ( s) ?;
368- doc. autoformat ( ) ;
373+ doc. ensure_v2 ( ) ;
369374 Ok ( doc. to_string ( ) )
370375 }
376+
377+ /// Takes a KDL v2 document string and returns the same document, but
378+ /// autoformatted into valid KDL v2 syntax.
379+ #[ cfg( feature = "v1" ) ]
380+ pub fn v2_to_v1 ( s : & str ) -> Result < String , KdlError > {
381+ let mut doc = KdlDocument :: parse_v2 ( s) ?;
382+ doc. ensure_v1 ( ) ;
383+ Ok ( doc. to_string ( ) )
384+ }
385+
386+ /// Makes sure this document is in v2 format.
387+ #[ cfg( feature = "v1" ) ]
388+ pub fn ensure_v2 ( & mut self ) {
389+ // No need to touch KdlDocumentFormat, probably. In the longer term,
390+ // we'll want to make sure to parse out whitespace and comments and make
391+ // sure they're actually compliant, but this is good enough for now.
392+ for node in self . nodes_mut ( ) . iter_mut ( ) {
393+ node. ensure_v2 ( ) ;
394+ }
395+ }
396+
397+ /// Makes sure this document is in v1 format.
398+ #[ cfg( feature = "v1" ) ]
399+ pub fn ensure_v1 ( & mut self ) {
400+ // No need to touch KdlDocumentFormat, probably. In the longer term,
401+ // we'll want to make sure to parse out whitespace and comments and make
402+ // sure they're actually compliant, but this is good enough for now.
403+
404+ // the last node in v1 docs/children has to have a semicolon.
405+ let mut iter = self . nodes_mut ( ) . iter_mut ( ) . rev ( ) ;
406+ let last = iter. next ( ) ;
407+ let penult = iter. next ( ) ;
408+ if let Some ( last) = last {
409+ if let Some ( fmt) = last. format_mut ( ) {
410+ if !fmt. trailing . contains ( ";" )
411+ && fmt
412+ . trailing
413+ . chars ( )
414+ . any ( |c| crate :: v2_parser:: NEWLINES . iter ( ) . any ( |nl| nl. contains ( c) ) )
415+ {
416+ fmt. terminator = ";" . into ( ) ;
417+ }
418+ } else {
419+ let maybe_indent = {
420+ if let Some ( penult) = penult {
421+ if let Some ( fmt) = penult. format ( ) {
422+ fmt. leading . clone ( )
423+ } else {
424+ "" . into ( )
425+ }
426+ } else {
427+ "" . into ( )
428+ }
429+ } ;
430+ last. format = Some ( KdlNodeFormat {
431+ leading : maybe_indent,
432+ terminator : "\n " . into ( ) ,
433+ ..Default :: default ( )
434+ } )
435+ }
436+ }
437+ for node in self . nodes_mut ( ) . iter_mut ( ) {
438+ node. ensure_v1 ( ) ;
439+ }
440+ }
371441}
372442
373443#[ cfg( feature = "v1" ) ]
@@ -956,10 +1026,95 @@ inline { time; to; live "our" "dreams"; "y;all" }
9561026 Ok ( ( ) )
9571027 }
9581028
959- #[ ignore = "Formatting is still seriously broken, and this is gonna need some extra love." ]
9601029 #[ cfg( feature = "v1" ) ]
9611030 #[ test]
962- fn v1_to_v2 ( ) -> miette:: Result < ( ) > {
1031+ fn v1_v2_conversions ( ) -> miette:: Result < ( ) > {
1032+ let v1 = r##"
1033+ // If you'd like to override the default keybindings completely, be sure to change "keybinds" to "keybinds clear-defaults=true"
1034+ keybinds {
1035+ normal {
1036+ // uncomment this and adjust key if using copy_on_select=false
1037+ // bind "Alt c" { Copy; }
1038+ }
1039+ locked {
1040+ bind "Ctrl g" { SwitchToMode "Normal"; }
1041+ }
1042+ resize {
1043+ bind "Ctrl n" { SwitchToMode "Normal"; }
1044+ bind "h" "Left" { Resize "Increase Left"; }
1045+ bind "j" "Down" { Resize "Increase Down"; }
1046+ bind "k" "Up" { Resize "Increase Up"; }
1047+ bind "l" "Right" { Resize "Increase Right"; }
1048+ bind "H" { Resize "Decrease Left"; }
1049+ bind "J" { Resize "Decrease Down"; }
1050+ bind "K" { Resize "Decrease Up"; }
1051+ bind "L" { Resize "Decrease Right"; }
1052+ bind "=" "+" { Resize "Increase"; }
1053+ bind "-" { Resize "Decrease"; }
1054+ }
1055+ }
1056+ // Plugin aliases - can be used to change the implementation of Zellij
1057+ // changing these requires a restart to take effect
1058+ plugins {
1059+ tab-bar location="zellij:tab-bar"
1060+ status-bar location="zellij:status-bar"
1061+ welcome-screen location="zellij:session-manager" {
1062+ welcome_screen true
1063+ }
1064+ filepicker location="zellij:strider" {
1065+ cwd "\/"
1066+ }
1067+ }
1068+ mouse_mode false
1069+ mirror_session true
1070+ "## ;
1071+ let v2 = r##"
1072+ // If you'd like to override the default keybindings completely, be sure to change "keybinds" to "keybinds clear-defaults=true"
1073+ keybinds {
1074+ normal {
1075+ // uncomment this and adjust key if using copy_on_select=false
1076+ // bind "Alt c" { Copy; }
1077+ }
1078+ locked {
1079+ bind "Ctrl g" { SwitchToMode Normal; }
1080+ }
1081+ resize {
1082+ bind "Ctrl n" { SwitchToMode Normal; }
1083+ bind h Left { Resize "Increase Left"; }
1084+ bind j Down { Resize "Increase Down"; }
1085+ bind k Up { Resize "Increase Up"; }
1086+ bind l Right { Resize "Increase Right"; }
1087+ bind H { Resize "Decrease Left"; }
1088+ bind J { Resize "Decrease Down"; }
1089+ bind K { Resize "Decrease Up"; }
1090+ bind L { Resize "Decrease Right"; }
1091+ bind "=" + { Resize Increase; }
1092+ bind - { Resize Decrease; }
1093+ }
1094+ }
1095+ // Plugin aliases - can be used to change the implementation of Zellij
1096+ // changing these requires a restart to take effect
1097+ plugins {
1098+ tab-bar location=zellij:tab-bar
1099+ status-bar location=zellij:status-bar
1100+ welcome-screen location=zellij:session-manager {
1101+ welcome_screen #true
1102+ }
1103+ filepicker location=zellij:strider {
1104+ cwd "/"
1105+ }
1106+ }
1107+ mouse_mode #false
1108+ mirror_session #true
1109+ "## ;
1110+ pretty_assertions:: assert_eq!( KdlDocument :: v1_to_v2( v1) ?, v2, "Converting a v1 doc to v2" ) ;
1111+ pretty_assertions:: assert_eq!( KdlDocument :: v2_to_v1( v2) ?, v1, "Converting a v2 doc to v1" ) ;
1112+ Ok ( ( ) )
1113+ }
1114+
1115+ #[ cfg( feature = "v1" ) ]
1116+ #[ test]
1117+ fn v2_to_v1 ( ) -> miette:: Result < ( ) > {
9631118 let original = r##"
9641119// If you'd like to override the default keybindings completely, be sure to change "keybinds" to "keybinds clear-defaults=true"
9651120keybinds {
0 commit comments