### ----------------------------------------------------
### -- GUI::Editors
### -- Licenses: LGPL-3.0-or-later
### -- Authors: Francis Grizzly Smit <grizzly@smit.id.au>
### -- File: rakulib/GUI/Editors.rakumod
### ----------------------------------------------------
### -- Chunk 2 of 3
IO.copy("$editor-config/$file".IO, :createonly);
                        if $r {
                            $*ERR.say: "copied /etc/skel/.local/share/gui-editors/$file -> $editor-config/$file";
                        } else {
                            $*ERR.say: "could not copy /etc/skel/.local/share/gui-editors/$file -> $editor-config/$file";
                        }
                        $result ?&= $r;
                    }
                } else {
                    my Bool $r = generate-configs($file);
                    $*ERR.say: "generated $editor-config/$file" if $r;
                    $result ?&= $r;
                }
            }
        } elsif !&client-check(@client-config-files, $client-config) {
            $result ?&= &generate-local-configs($file, $client-config);
        }
    } # for @config-files -> $file # 

    # The default name of the gui editor #
    my $actions = EditorsActions;
    my @editors-file = "$editor-config/editors".IO.slurp.split("\n");
    #dd @editors-file;
    #my $edtest = Editors.parse(@editors-file.join("\x0A"), :enc('UTF-8'), :$actions).made;
    #dd $edtest;
    @GUIEDITORS = Editors.parse(@editors-file.join("\x0A"), :enc('UTF-8'), :$actions).made;
    @gui-editors = @GUIEDITORS.grep( -> %l { %l«type» eq 'config-line' } ).map: -> %ln { %ln«value»; };
    @default-editors = @GUIEDITORS.grep( -> %l { %l«type» eq 'editor-to-use' } ).map: -> %ln { %ln«value»; };
    if @default-editors > 1 {
        $*ERR.say: "Error: file $editor-config/editors is miss configured  more than one editor defined should be 0 or 1";
    }
    @override-gui_editor = @GUIEDITORS.grep( -> %l { %l«type» eq 'override-gui_editor' } ).map: -> %ln { %ln«value»; };
    if @override-gui_editor > 1 {
        my Int:D $elems = @override-gui_editor.elems;
        $*ERR.say: qq[Make up your mind only one "override GUI_EDITOR" is required, you supplied $elems are you insane???];
        $override-GUI_EDITOR = True;
    } elsif @override-gui_editor == 1 {
        $override-GUI_EDITOR = True;
    }
    #my Str @gui-editors = slurp("$editor-config/editors").split("\n").map( { my Str $e = $_; $e ~~ s/ '#' .* $$ //; $e } ).map( { $_.trim() } ).grep: { !rx/ [ ^^ \s* '#' .* $$ || ^^ \s* $$ ] / };
    #my Str @guieditors;
    #@gui-editors.raku.say;
    if @gui-editors {
        #@gui-editors.raku.say;
        for @gui-editors -> $geditor {
            if !@guieditors.grep: { $geditor } {
                my Str $guieditor = $geditor;
                $guieditor .=trim;
                @guieditors.append($guieditor);
            }
        }
    }

    if $override-GUI_EDITOR && @default-editors {
        $editor = @default-editors[@default-editors - 1];
    }elsif %*ENV<GUI_EDITOR>:exists {
        my Str $guieditor = ~%*ENV<GUI_EDITOR>;
        if ! @guieditors.grep( { $_ eq $guieditor.IO.basename } ) {
            @guieditors.prepend($guieditor.IO.basename);
        }
    } elsif $editor-guessed && @default-editors {
        $editor = @default-editors[@default-editors - 1];
    }
    return $result;
} #`««« sub init-gui-editors(Str:D @client-config-files, Str:D $client-config-path, &gen-configs:(Str:D, Str:D --> Bool:D),
                                                            &check:(Str:D @cfg-files, Str:D $config --> Bool:D) --> Bool:D) is  export »»»

#`«««
    ##################################
    #********************************#
    #*                              *#
    #*       Editor functions       *#
    #*                              *#
    #********************************#
    ##################################
#»»»

=begin pod

=head2 Editor functions

=head3 list-editors(…)

List all known GUI Editors, flagging the selected editor with B<'*'>
note if none is flagged either B<C<$editor>> is set to a non GUI Editor 
or B<C<$editor>> is set to the empty string.

=begin code :lang<raku>

sub list-editors(Str:D $prefix,
                 Bool:D $colour,
                 Bool:D $syntax,
                 Int:D $page-length,
                 Regex:D $pattern --> Bool) is export 

=end code

=end pod

sub list-editors(Str:D $prefix,
                 Bool:D $colour,
                 Bool:D $syntax,
                 Int:D $page-length,
                 Regex:D $pattern --> Bool) is export {
    my Int:D $cnt = 0;
    my Str:D $mark = '';
    my @all-guieditors;
    for (|@guieditors, |@gui-editors) -> $ed {
        my %row = editor => $ed, mark => (($editor.trim.IO.basename eq $ed.trim.IO.basename) ?? '*' !! '');
        @all-guieditors.push: %row if !@all-guieditors.grep: -> %row { $ed eq %row«editor» };
    }
    my Str:D @fields     = 'editor', 'mark';
    my Str:D %fancynames = editor => 'Editors', mark => 'Actual Editor';
    my       %defaults;
    my Str:D %flags      = editor => '-', mark => '';
    sub include-row(Str:D $prefix, Regex:D $pattern, Int:D $idx, Str:D @fields, %row --> Bool:D) {
        my Str:D $ed = ~%row«editor»;
        return True if $ed.starts-with($prefix, :ignorecase) && $ed ~~ $pattern;
        return False;
    } # sub include-row(Str:D $prefix, Regex:D $pattern, Int:D $idx, Str:D @fields, %row --> Bool:D) #
    sub head-value(Int:D $indx, Str:D $field, Bool:D $colour, Bool:D $syntax, Str:D @fields --> Str:D) {
        if $syntax {
            return t.color(0, 255, 255) ~ %fancynames{$field};
        } elsif $colour {
            return t.color(0, 255, 255) ~ %fancynames{$field};
        } else {
            return ~%fancynames{$field};
        }
    } # sub head-value(Int:D $indx, Str:D $field, Bool:D $colour, Bool:D $syntax, Str:D @fields --> Str:D) #
    sub head-between(Int:D $indx, Str:D $field, Bool:D $colour, Bool:D $syntax, Str:D @fields --> Str:D) {
        if $field eq 'editor' {
            if $syntax {
                return t.bright-blue ~ ' | ';
            } elsif $colour {
                return t.bright-blue ~ ' | ';
            } else {
                return ' | ';
            }
        } else {
            return '  ';
        }
    } # sub head-between(Int:D $indx, Str:D $field, Bool:D $colour, Bool:D $syntax, Str:D @fields --> Str:D) #
    sub field-value(Int:D $idx, Str:D $field, $value, Bool:D $colour, Bool:D $syntax, Str:D @fields, %row --> Str:D) {
        if $syntax {
            given $field {
                when 'editor' { return t.color(0,255,0) ~ ~$value; }
                when 'mark'   { return t.color(255,0,0) ~ ~$value; }
                default       { return t.color(255,0,0) ~ '';      }
            }
        } elsif $colour {
            return t.color(0,0,255) ~ ~$value;
        } else {
            return ~$value
        }
    } # sub field-value(Int:D $idx, Str:D $field, $value, Bool:D $colour, Bool:D $syntax, Str:D @fields, %row --> Str:D) #
    sub between(Int:D $idx, Str:D $field, Bool:D $colour, Bool:D $syntax, Str:D @fields, %row --> Str:D) {
        if $field eq 'editor' {
            if $syntax {
                return t.bright-blue ~ ' | ';
            } elsif $colour {
                return t.bright-blue ~ ' | ';
            } else {
                return ' | ';
            }
        } else {
            return '  ';
        }
    } # sub between(Int:D $idx, Str:D $field, Bool:D $colour, Bool:D $syntax, Str:D @fields, %row --> Str:D) #
    sub row-formatting(Int:D $cnt, Bool:D $colour, Bool:D $syntax --> Str:D) {
        if $colour {
            if $syntax { 
                return t.bg-color(255, 0, 255) ~ t.bold ~ t.bright-blue if $cnt == -3; # three heading lines. #
                return t.bg-color(0, 0, 127) ~ t.bold ~ t.bright-blue if $cnt == -2;
                return t.bg-color(255, 0, 255) ~ t.bold ~ t.bright-blue if $cnt == -1;
                return (($cnt % 2 == 0) ?? t.bg-yellow !! t.bg-color(0,195,0)) ~ t.bold ~ t.bright-blue;
            } else {
                return t.bg-color(255, 0, 255) ~ t.bold ~ t.bright-blue if $cnt == -3;
                return t.bg-color(0, 0, 127) ~ t.bold ~ t.bright-blue if $cnt == -2;
                return t.bg-color(255, 0, 255) ~ t.bold ~ t.bright-blue if $cnt == -1;
                return (($cnt % 2 == 0) ?? t.bg-yellow !! t.bg-color(0,195,0)) ~ t.bold ~ t.bright-blue;
            }
        } else {
            return '';
        }
    } # sub row-formatting(Int:D $cnt, Bool:D $colour, Bool:D $syntax --> Str:D) #
    return list-by($prefix, $colour, $syntax, $page-length,
                  $pattern, @fields, %defaults, @all-guieditors,
                  :%flags, 
                  :&include-row, 
                  :&head-value, 
                  :&head-between,
                  :&field-value, 
                  :&between,
                  :&row-formatting);
} #`««« sub list-editors(Str:D $prefix,
                 Bool:D $colour,
                 Bool:D $syntax,
                 Int:D $page-length,
                 Regex:D $pattern --> Bool) is export »»»

=begin pod

=head3 editors-stats(…)

Show the values of some editors parameters.

=begin code :lang<raku>

sub editors-stats(Str:D $prefix,
                  Bool:D $colour,
                  Bool:D $syntax,
                  Int:D $page-length,
                  Regex:D $pattern --> Bool) is export 

=end code

L<Top of Document|#table-of-contents>

=end pod

sub editors-stats(Str:D $prefix,
                  Bool:D $colour,
                  Bool:D $syntax,
                  Int:D $page-length,
                  Regex:D $pattern --> Bool) is export {
    my Int:D $cnt = 0;
    my %editors = '%*ENV«GUI_EDITOR»' => $GUI_EDITOR,
                  '%*ENV<VISUAL>' => $VISUAL,
                  '%*ENV<EDITOR>' => $EDITOR,
                  '$editor' => $editor,
                  '$override-GUI_EDITOR' => $override-GUI_EDITOR,
                  '@default-editors' => '[ ' ~ ((@default-editors) ?? '"' ~ @default-editors.join('", "') ~ '"' !! '') ~ ' ]', 
                  '@override-gui_editor' => '[' ~ @override-gui_editor.join(', ') ~ ']';
    my @rows;
    for %editors.kv -> $var, $value {
        my %h = var => $var, value => $value;
        @rows.push: %h;
    } # for %editors.kv -> $var, $value #
    my Str:D @fields     = 'var', 'value';
    my Str:D %fancynames = var => 'Variable', value => 'Value';
    my       %defaults;
    my Str:D %flags      = var => '-', value => '';
    sub include-row(Str:D $prefix, Regex:D $pattern, Int:D $idx, Str:D @fields, %row --> Bool:D) {
        for @fields -> $field {
            my Str:D $value = ~%row{$field};
            return True if $value.starts-with($prefix, :ignorecase) && $value ~~ $pattern;
        }
        return False;
    } # sub include-row(Str:D $prefix, Regex:D $pattern, Int:D $idx, Str:D @fields, %row --> Bool:D) #
    sub head-value(Int:D $indx, Str:D $field, Bool:D $colour, Bool:D $syntax, Str:D @fields --> Str:D) {
        if $syntax {
            return t.color(0, 255, 255) ~ %fancynames{$field};
        } elsif $colour {
            return t.color(0, 255, 255) ~ %fancynames{$field};
        } else {
            return ~%fancynames{$field};
        }
    } # sub head-value(Int:D $indx, Str:D $field, Bool:D $colour, Bool:D $syntax, Str:D @fields --> Str:D) #
    sub head-between(Int:D $indx, Str:D $field, Bool:D $colour, Bool:D $syntax, Str:D @fields --> Str:D) {
        if $field eq 'var' {
            if $syntax {
                return t.bright-blue ~ '   |   ';
            } elsif $colour {
                return t.bright-blue ~ '   |   ';
            } else {
                return '   |   ';
            }
        } else {
            return '  ';
        }
    } # sub head-between(Int:D $indx, Str:D $field, Bool:D $colour, Bool:D $syntax, Str:D @fields --> Str:D) #
    sub field-value(Int:D $idx, Str:D $field, $value, Bool:D $colour, Bool:D $syntax, Str:D @fields, %row --> Str:D) {
        if $syntax {
            given $field {
                when 'var'   { return highlight-var(~$value); }
                when 'value' { return highlight-val(~$value); }
                default      { return '';      }
            }
        } elsif $colour {
            return t.color(0,0,255) ~ ~$value;
        } else {
            return ~$value
        }
    } # sub field-value(Int:D $idx, Str:D $field, $value, Bool:D $colour, Bool:D $syntax, Str:D @fields, %row --> Str:D) #
    sub between(Int:D $idx, Str:D $field, Bool:D $colour, Bool:D $syntax, Str:D @fields, %row --> Str:D) {
        if $field eq 'var' {
            if $syntax {
                return t.bright-blue ~ '   |   ';
            } elsif $colour {
                return t.bright-blue ~ '   |   ';
            } else {
                return '   |   ';
            }
        } else {
            return '  ';
        }
    } # sub between(Int:D $idx, Str:D $field, Bool:D $colour, Bool:D $syntax, Str:D @fields, %row --> Str:D) #
    sub row-formatting(Int:D $cnt, Bool:D $colour, Bool:D $syntax --> Str:D) {
        if $colour {
            if $syntax { 
                return t.bg-color(255, 0, 255) ~ t.bold ~ t.bright-blue if $cnt == -3; # three heading lines. #
                return t.bg-color(0, 0, 127) ~ t.bold ~ t.bright-blue if $cnt == -2;
                return t.bg-color(255, 0, 255) ~ t.bold ~ t.bright-blue if $cnt == -1;
                return (($cnt % 2 == 0) ?? t.bg-yellow !! t.bg-color(0,195,0)) ~ t.bold ~ t.bright-blue;
            } else {
                return t.bg-color(255, 0, 255) ~ t.bold ~ t.bright-blue if $cnt == -3;
                return t.bg-color(0, 0, 127) ~ t.bold ~ t.bright-blue if $cnt == -2;
                return t.bg-color(255, 0, 255) ~ t.bold ~ t.bright-blue if $cnt == -1;
                return (($cnt % 2 == 0) ?? t.bg-yellow !! t.bg-color(0,195,0)) ~ t.bold ~ t.bright-blue;
            }
        } else {
            return '';
        }
    } # sub row-formatting(Int:D $cnt, Bool:D $colour, Bool:D $syntax --> Str:D) #
    return list-by($prefix, $colour, $syntax, $page-length,
                  $pattern, @fields, %defaults, @rows,
                  :%flags, 
                  :&include-row, 
                  :&head-value, 
                  :&head-between,
                  :&field-value, 
                  :&between,
                  :&row-formatting);
} #`««« sub editors-stats(Str:D $prefix,
                  Bool:D $colour,
                  Bool:D $syntax,
                  Int:D $page-length,
                  Regex:D $pattern --> Bool) is export »»»

=begin pod

=head3 BadEditor

B<C<BadEditor>> is an Exception class for the B<C<GUI::Editors>> module.

=begin code :lang<raku>

class BadEditor is Exception is export {
    has Str:D $.msg = 'Error: bad editor specified';
    method message( --> Str:D) {
        $!msg;
    }
}

=end code

=end pod

class BadEditor is Exception is export {
    has Str:D $.msg = 'Error: bad editor specified';
    method message( --> Str:D) {
        $!msg;
    }
}

=begin pod

=head3 set-editor(…)

A function to set the editor of choice.

=begin code :lang<raku>

sub set-editor(Str:D $editor, Str $comment = Str --> Bool:D) is export 

=end code

B<NB: this will still be overridden by C<%*ENV«GUI_EDITOR»> unless you set B<override GUI_EDITOR>>.

=end pod

sub set-editor(Str:D $editor, Str $comment = Str --> Bool:D) is export {
    if @default-editors.elems == 1 && (@default-editors[0] eq $editor) {
        return True; # Already set to this value #
    }
    my Str:D $line = "        editor      :=  $editor";
    with $comment {
        $line ~= " # $comment";
    }
    my $actions = EditorLineActions;
    my IO::Handle:D $input  = "$editor-config/editors".IO.open:     :r, :nl-in("\n")   :chomp;
    my IO::Handle:D $output = "$editor-config/editors.new".IO.open: :w, :nl-out("\n"), :chomp;
    my Bool:D $unset = True;
    my Str $ln;
    $ln = $input.get;
    while !$input.eof {
        my $ed-line = EditorLine.parse($ln, :enc('UTF-8'), :$actions).made;
        if $ed-line {
            if $unset {
                $output.say: $line;
                $unset = False;
            }
        } else {
            $output.say: $ln;
        }
        $ln = $input.get;
    } # while !$input.eof #
    if $ln { # last line in file #
        my $ed-line = EditorLine.parse($ln, :enc('UTF-8'), :$actions).made;
        if $ed-line {
            if $unset {
                $output.say: $line;
                $unset = False;
            }
        } else {
            $output.say: $ln;
        }
    } # if $ln #
    if $unset { # can happen if editor was not set before #
        $output.say: $line;
        $unset = False;
    }
    $input.close;
    $output.close;
    if $unset {
        $*ERR.say: qq[Error: editors: $editor "not set"];
        return "$editor-config/editors.new".IO.unlink;
    } elsif "$editor-config/editors.new".IO.move: "$editor-config/editors" {
        return True;
    } else {
        BadEditor.new(:msg("move failed")).throw;
    }
    return !$unset; # should never be reached #
} #`««« sub set-editor(Str:D $editor, Str $comment = Str --> Bool:D) is export »»»

=begin pod

=head3 add-gui-editor(…)

Add an editor to the list of known GUI Editors.

=begin code :lang<raku>

sub add-gui-editor(Str:D $editor, Str $comment = Str --> Bool:D) is export 

=end code

B<NB: please make sure it really is a GUI Editor otherwise this module will
not work correctly. You are completely free to set the chosen editor to what
ever you like.>

=end pod

sub add-gui-editor(Str:D $editor, Str $comment = Str --> Bool:D) is export {
    if @gui-editors.grep( -> $ged { $ged eq $editor }) {
        return True; # already in there. #
    }
    my Str:D $line = "        guieditors  +=  $editor";
    with $comment {
        $line ~= " # $comment";
    }
    my IO::Handle:D $output  = "$editor-config/editors".IO.open:     :a, :nl-in("\n")   :chomp;
    $output.say: $line;
    return $output.close;
} #`««« sub add-gui-editor(Str:D $editor --> Bool:D) is export »»»

=begin pod

=head3 set-override-GUI_EDITOR(…)

Set or unset the B<override GUI_EDITOR> flag.

=begin code :lang<raku>

sub set-override-GUI_EDITOR(Bool:D $value, Str $comment = Str --> Bool:D) is export 

=end code

If set then the file always wins else B<C<%*ENV«GUI_EDITOR»>> always wins if set.

L<Top of Document|#table-of-contents>

=end pod

sub set-override-GUI_EDITOR(Bool:D $value, Str $comment = Str --> Bool:D) is export {
    if @override-gui_editor.elems == 1 && (@override-gui_editor[0] eq $editor) {
        return True; # Already set to this value #
    }
    my Str:D $line = "        override GUI_EDITOR";
    $line          = "        #override GUI_EDITOR" unless $value;
    with $comment {
        $line ~= " # $comment";
    }
    my $actions = OverrideGUIEditorActions;
    my IO::Handle:D $input  = "$editor-config/editors".IO.open:     :r, :nl-in("\n")   :chomp;
    my IO::Handle:D $output = "$editor-config/editors.new".IO.open: :w, :nl-out("\n"), :chomp;
    my Bool:D $unset = True;
    my Str $ln;
    $ln = $input.get;
    while !$input.eof {
        my $ed-line = OverrideGUIEditor.parse($ln, :enc('UTF-8'), :$actions).made;
        if $ed-line {
            if $unset {
                $output.say: $line;
                $unset = False;
            }
        } else {
            $output.say: $ln;
        }
        $ln = $input.get;
    } # while !$input.eof #
    if $ln { # last line in file #
        my $ed-line = OverrideGUIEditor.parse($ln, :enc('UTF-8'), :$actions).made;
        if $ed-line {
            if $unset {
                $output.say: $line;
                $unset = False;
            }
        } else {
            $output.say: $ln;
        }
    } # if $ln #
    if $unset { # can happen if editor was not set before #
        $output.say: $line;
        $unset = False;
    }
    $input.close;
    $output.close;
    if $unset {
        $*ERR.say: qq[Error: editors: $editor "not set"];
        return "$editor-config/editors.new".IO.unlink;
    } elsif "$editor-config/editors.new".IO.move: "$editor-config/editors" {
        return True;
    } else {
        BadEditor.new(:msg("move failed")).throw;
    }
    return !$unset; # should never be reached #
} #`««« sub set-override-GUI_EDITOR(Bool:D $value, Str $comment = Str --> Bool:D) is export »»»

=begin pod

=head3 backup-editors(…)

Backup the editors file.

=begin code :lang<raku>

sub backup-editors(Bool:D $use-windows-formatting --> Bool) is export 

=end code

B«NB: if $use-windows-formatting is true or the program is running on windows then
C<.> will become C<·> and C<:> will become C<.>, this is to avoid
problems with the special meaning of C<:> on windows.»

=end pod

sub backup-editors(Bool:D $use-windows-formatting --> Bool) is export {
    my DateTime $now = DateTime.now;
    my Str:D $time-stamp = $now.Str;
    if $*DISTRO.is-win || $use-windows-formatting {
        $time-stamp ~~ tr/./·/;
        $time-stamp ~~ tr/:/./;
    }
    return "$editor-config/editors".IO.copy("$editor-config/editors.$time-stamp".IO);
} # sub backup-editors(Bool:D $use-windows-formatting --> Bool) is export #

=begin pod

L<Top of Document|#table-of-contents>

=head3 restore-editors(…)

Restore the editors file from a backup.

=begin code :lang<raku>

sub restore-editors(IO::Path $restore-from --> Bool) is export 

=end code

If B<C<$restore-from>> is relative and not found from the current directory 
B<C<$editor-config/$restore-from>> will be tried. 

=end pod

sub restore-editors(IO::Path $restore-from is copy --> Bool) is export {
    with $restore-from {
        my $actions = EditorsActions;
        if $restore-from ~~ :f {
            my @ed-file = $restore-from.slurp.split("\n");
            return $restore-from.copy("$editor-config/editors".IO) if  Editors.parse(@ed-file.join("\x0A"), :enc('UTF-8'), :$actions).made;
        } elsif $restore-from.is-relative && "$editor-config/{$restore-from.Str}".IO ~~ :f {
            $restore-from = "$editor-config/{$restore-from.Str}".IO;
            my @ed-file = $restore-from.slurp.split("\n");
            return $restore-from.copy("$editor-config/editors".IO) if  Editors.parse(@ed-file.join("\x0A"), :enc('UTF-8'), :$actions).made;
        }
        return False;
    } else {
        return False;
    }
}

=begin pod

=head3 list-editors-backups(…)

List all the available backups in the B<C<$editor-config>>.

=begin code :lang<raku>

sub list-editors-backups(Str:D $prefix,
                         Bool:D $colour is copy,
                         Bool:D $syntax,
                         Regex:D $pattern,
                         Int:D $page-length --> Bool:D) is export

=end code

=item1 Where
=item2 B<C<$prefix>>       a filter only files starting with this are included in the result.
=item2 B<C<$colour>>       colour the output.
=item2 B<C<$syntax>>       syntax highlight the results colour on steroids.
=item2 B<C<$pattern>>      a regex to filter the results by only files matching this will be included in the results.
=item2 B<C<$page-length>>  set the length of the pages before it repeats the header.

L<Top of Document|#table-of-contents>

=end pod

sub list-editors-backups(Str:D $prefix,
                         Bool:D $colour is copy,
                         Bool:D $syntax,
                         Regex:D $pattern,
                         Int:D $page-length --> Bool:D) is export {
    $colour = True if $syntax;
    my IO::Path @backups = $editor-config.IO.dir(:test(rx/ ^ 
                                                           'editors.' \d ** 4 '-' \d ** 2 '-' \d ** 2
                                                               [ 'T' \d **2 [ [ '.' || ':' ] \d ** 2 ] ** {0..2} [ [ '.' || '·' ] \d+ 
                                                                   [ [ '+' || '-' ] \d ** 2 [ '.' || ':' ] \d ** 2 || 'z' ]?  ]?
                                                               ]?
                                                           $
                                                         /
                                                       )
                                                );
    my $actions = EditorsActions;
    @backups .=grep: -> IO::Path $fl { 
                                my @file = $fl.slurp.split("\n");
                                Editors.parse(@file.join("\x0A"), :enc('UTF-8'), :$actions).made;
                            };
    @backups .=sort;
    my @_backups = @backups.map: -> IO::Path $f {
          my %elt = backup => $f.basename, perms => symbolic-perms($f, :$colour, :$syntax),
                      user => $f.user, group => $f.group, size => $f.s, modified => $f.modified;
          %elt;
    };
    my Str:D @fields = 'perms', 'size', 'user', 'group', 'modified', 'backup';
    my       %defaults;
    my Str:D %fancynames = perms => 'Permissions', size => 'Size',
                             user => 'User', group => 'Group',
                             modified => 'Date Modified', backup => 'Backup';
    sub include-row(Str:D $prefix, Regex:D $pattern, Int:D $idx, Str:D @fields, %row --> Bool:D) {
        my Str:D $value = ~(%row«backup» // '');
        return True if $value.starts-with($prefix, :ignorecase) && $value ~~ $pattern;
        return False;
    } # sub include-row(Str:D $prefix, Regex:D $pattern, Int:D $idx, Str:D @fields, %row --> Bool:D) #
    sub head-value(Int:D $indx, Str:D $field, Bool:D $colour, Bool:D $syntax, Str:D @fields --> Str:D) {
        #dd $indx, $field, $colour, $syntax, @fields;
        if $colour {
            if $syntax { 
                return t.color(0, 255, 255) ~ %fancynames{$field};
            } else {
                return t.color(0, 255, 255) ~ %fancynames{$field};
            }
        } else {
            return %fancynames{$field};
        }
    } # sub head-value(Int:D $indx, Str:D $field, Bool:D $colour, Bool:D $syntax, Str:D @fields --> Str:D) #
    sub head-between(Int:D $indx, Str:D $field, Bool:D $colour, Bool:D $syntax, Str:D @fields --> Str:D) {
        return ' ';
    } # sub head-between(Int:D $indx, Str:D $field, Bool:D $colour, Bool:D $syntax, Str:D @fields --> Str:D) #
    sub field-value(Int:D $idx, Str:D $field, $value, Bool:D $colour, Bool:D $syntax, Str:D @fields, %row --> Str:D) {
        my Str:D $val = ~($value // ''); #`««« assumming $value is a Str:D »»»
        #dd $val, $value, $field;
        if $syntax {
            given $field {
                when 'perms'    { return $val; }
                when 'size'     {
                    my Int:D $size = +$value;
                    return t.color(255, 0, 0) ~ format-bytes($size);
                }
                when 'user'     { return t.color(255, 255, 0) ~ uid2username(+$value);    }
                when 'group'    { return t.color(255, 255, 0) ~ gid2groupname(+$value);   }
                when 'modified' {
                    my Instant:D $m = +$value;
                    my DateTime:D $dt = $m.DateTime.local;
                    return t.color(0, 0, 235) ~ $dt.Str;  
                }
                when 'backup'   { return t.color(255, 0, 255) ~ $val; }
                default         { return t.color(255, 0, 0) ~ $val;   }
            } # given $field #
        } elsif $colour {
            given $field {
                when 'perms'    { return $val; }
                when 'size'     {
                    my Int:D $size = +$value;
                    return t.color(0, 0, 255) ~ format-bytes($size);
                }
                when 'user'     { return t.color(0, 0, 255) ~ uid2username(+$value);    }
                when 'group'    { return t.color(0, 0, 255) ~ gid2groupname(+$value);   }
                when 'modified' {
                    my Instant:D $m = +$value;
                    my DateTime:D $dt = $m.DateTime.local;
                    return t.color(0, 0, 255) ~ $dt.Str;  
                }
                when 'backup'   { return t.color(0, 0, 255) ~ $val;   }
                default         { return t.color(255, 0, 0) ~ $val;   }
            } # given $field #
        } else {
            given $field {
                when 'perms'    { return $val; }
                when 'size'     {
                    my Int:D $size = +$value;
                    return format-bytes($size);
                }
                when 'user'     { return uid2username(+$value);    }
                when 'group'    { return gid2groupname(+$value);   }
                when 'modified' {
                    my Instant:D $m = +$value;
                    my DateTime:D $dt = $m.DateTime.local;
                    return $dt.Str;  
                }
                when 'backup'   { return $val;   }
                default         { return $val;   }
            } # given $field #
        }
    } # sub field-value(Int:D $idx, Str:D $field, $value, Bool:D $colour, Bool:D $syntax, Str:D @fields, %row --> Str:D) #
    sub between(Int:D $idx, Str:D $field, Bool:D $colour, Bool:D $syntax, Str:D @fields, %row --> Str:D) {
        return ' ';
    } # sub between(Int:D $idx, Str:D $field, Bool:D $colour, Bool:D $syntax, Str:D @fields, %row --> Str:D) #
    sub row-formatting(Int:D $cnt, Bool:D $colour, Bool:D $syntax --> Str:D) {
        if $colour {
            if $syntax { 
                return t.bg-color(255, 0, 255) ~ t.bold ~ t.bright-blue if $cnt == -3; # three heading lines. #
                return t.bg-color(0, 0, 127) ~ t.bold ~ t.bright-blue if $cnt == -2;
                return t.bg-color(255, 0, 255) ~ t.bold ~ t.bright-blue if $cnt == -1;
                return (($cnt % 2 == 0) ?? t.bg-yellow !! t.bg-color(0,195,0