@@ -1013,6 +1013,130 @@ sub color_diff {
1013
1013
} @_ ;
1014
1014
}
1015
1015
1016
+ sub label_hunk_lines {
1017
+ local $_ ;
1018
+ my $hunk = shift ;
1019
+ my $i = 0;
1020
+ my $labels = [ map { / ^[-+]/ ? ++$i : 0 } @{$hunk -> {TEXT }} ];
1021
+ if ($i > 1) {
1022
+ @{$hunk }{qw( LABELS MAX_LABEL) } = ($labels , $i );
1023
+ return 1;
1024
+ }
1025
+ return 0;
1026
+ }
1027
+
1028
+ sub select_hunk_lines {
1029
+ my ($hunk , $selected ) = @_ ;
1030
+ my ($text , $labels ) = @{$hunk }{qw( TEXT LABELS) };
1031
+ my ($i , $o_cnt , $n_cnt ) = (0, 0, 0);
1032
+ my ($push_eol , @newtext );
1033
+ # Lines with this mode will become context lines if they are
1034
+ # not selected
1035
+ my $context_mode = $patch_mode_flavour {IS_REVERSE } ? ' +' : ' -' ;
1036
+ for $i (1..$# {$text }) {
1037
+ my $mode = substr ($text -> [$i ], 0, 1);
1038
+ if ($mode eq ' \\ ' ) {
1039
+ push @newtext , $text -> [$i ] if ($push_eol );
1040
+ undef $push_eol ;
1041
+ } elsif ($labels -> [$i ] and $selected -> [$labels -> [$i ]]) {
1042
+ push @newtext , $text -> [$i ];
1043
+ if ($mode eq ' +' ) {
1044
+ $n_cnt ++;
1045
+ } else {
1046
+ $o_cnt ++;
1047
+ }
1048
+ $push_eol = 1;
1049
+ } elsif ($mode eq ' ' or $mode eq $context_mode ) {
1050
+ push @newtext , ' ' . substr ($text -> [$i ], 1);
1051
+ $o_cnt ++; $n_cnt ++;
1052
+ $push_eol = 1;
1053
+ } else {
1054
+ undef $push_eol ;
1055
+ }
1056
+ }
1057
+ my ($o_ofs , $orig_o_cnt , $n_ofs , $orig_n_cnt ) =
1058
+ parse_hunk_header($text -> [0]);
1059
+ unshift @newtext , format_hunk_header($o_ofs , $o_cnt , $n_ofs , $n_cnt );
1060
+ my $newhunk = {
1061
+ TEXT => \@newtext ,
1062
+ DISPLAY => [ color_diff(@newtext ) ],
1063
+ OFS_DELTA => $orig_o_cnt - $orig_n_cnt - $o_cnt + $n_cnt ,
1064
+ TYPE => $hunk -> {TYPE },
1065
+ USE => 1,
1066
+ };
1067
+ # If this hunk has previously been edited add the offset delta
1068
+ # of the old hunk to get the real delta from the original
1069
+ # hunk.
1070
+ if ($hunk -> {OFS_DELTA }) {
1071
+ $newhunk -> {OFS_DELTA } += $hunk -> {OFS_DELTA };
1072
+ }
1073
+ return $newhunk ;
1074
+ }
1075
+
1076
+ sub check_hunk_label {
1077
+ my ($max_label , $label ) = ($_ [0]-> {MAX_LABEL }, $_ [1]);
1078
+ if ($label < 1 or $label > $max_label ) {
1079
+ error_msg sprintf (__(" invalid hunk line '%d '\n " ), $label );
1080
+ return 0;
1081
+ }
1082
+ return 1;
1083
+ }
1084
+
1085
+ sub parse_hunk_selection {
1086
+ local $_ ;
1087
+ my ($hunk , $line ) = @_ ;
1088
+ my $max_label = $hunk -> {MAX_LABEL };
1089
+ my @selected = (0) x ($max_label + 1);
1090
+ my @fields = split (/ [,\s ]+/ , $line );
1091
+ for (@fields ) {
1092
+ if (my ($lo , $hi ) = /^([0-9]+)-([0-9]*)$/ ) {
1093
+ if ($hi eq ' ' ) {
1094
+ $hi = $max_label ;
1095
+ }
1096
+ check_hunk_label($hunk , $lo ) or return undef ;
1097
+ check_hunk_label($hunk , $hi ) or return undef ;
1098
+ if ($hi < $lo ) {
1099
+ ($lo , $hi ) = ($hi , $lo );
1100
+ }
1101
+ @selected [$lo ..$hi ] = (1) x (1 + $hi - $lo );
1102
+ } elsif (/ ^([0-9]+)$ / ) {
1103
+ check_hunk_label($hunk , $1 ) or return undef ;
1104
+ $selected [$1 ] = 1;
1105
+ } else {
1106
+ error_msg sprintf (__(" invalid hunk line '%s '\n " ), $_ );
1107
+ return undef ;
1108
+ }
1109
+ }
1110
+ return \@selected ;
1111
+ }
1112
+
1113
+ sub display_hunk_lines {
1114
+ my ($display , $labels , $max_label ) =
1115
+ @{$_ [0]}{qw( DISPLAY LABELS MAX_LABEL) };
1116
+ my $width = int (log ($max_label ) / log (10)) + 1;
1117
+ my $padding = ' ' x ($width + 1);
1118
+ for my $i (0..$# {$display }) {
1119
+ if ($labels -> [$i ]) {
1120
+ printf ' %*d %s' , $width , $labels -> [$i ], $display -> [$i ];
1121
+ } else {
1122
+ print $padding . $display -> [$i ];
1123
+ }
1124
+ }
1125
+ }
1126
+
1127
+ sub select_lines_loop {
1128
+ my $hunk = shift ;
1129
+ display_hunk_lines($hunk );
1130
+ my $selection = undef ;
1131
+ until (defined $selection ) {
1132
+ print colored $prompt_color , __(" select lines? " );
1133
+ my $text = <STDIN >;
1134
+ defined $text and $text =~ / \S / or return undef ;
1135
+ $selection = parse_hunk_selection($hunk , $text );
1136
+ }
1137
+ return select_hunk_lines($hunk , $selection );
1138
+ }
1139
+
1016
1140
my %edit_hunk_manually_modes = (
1017
1141
stage => N__(
1018
1142
" If the patch applies cleanly, the edited hunk will immediately be
@@ -1255,6 +1379,7 @@ sub help_patch_cmd {
1255
1379
J - leave this hunk undecided, see next hunk
1256
1380
k - leave this hunk undecided, see previous undecided hunk
1257
1381
K - leave this hunk undecided, see previous hunk
1382
+ l - select hunk lines to use
1258
1383
s - split the current hunk into smaller hunks
1259
1384
e - manually edit the current hunk
1260
1385
? - print help
@@ -1471,6 +1596,9 @@ sub patch_update_file {
1471
1596
if ($hunk [$ix ]{TYPE } eq ' hunk' ) {
1472
1597
$other .= ' ,e' ;
1473
1598
}
1599
+ if (label_hunk_lines($hunk [$ix ])) {
1600
+ $other .= ' ,l' ;
1601
+ }
1474
1602
for (@{$hunk [$ix ]{DISPLAY }}) {
1475
1603
print ;
1476
1604
}
@@ -1610,6 +1738,18 @@ sub patch_update_file {
1610
1738
next ;
1611
1739
}
1612
1740
}
1741
+ elsif ($line =~ / ^l/ ) {
1742
+ unless ($other =~ / l/ ) {
1743
+ error_msg __(" Cannot select line by line\n " );
1744
+ next ;
1745
+ }
1746
+ my $newhunk = select_lines_loop($hunk [$ix ]);
1747
+ if ($newhunk ) {
1748
+ splice @hunk , $ix , 1, $newhunk ;
1749
+ } else {
1750
+ next ;
1751
+ }
1752
+ }
1613
1753
elsif ($other =~ / s/ && $line =~ / ^s/ ) {
1614
1754
my @split = split_hunk($hunk [$ix ]{TEXT }, $hunk [$ix ]{DISPLAY });
1615
1755
if (1 < @split ) {
0 commit comments