@@ -542,10 +542,23 @@ proc apply_hunk {x y} {
542542 }
543543}
544544
545- proc apply_line {x y} {
545+ proc apply_range_or_line {x y} {
546546 global current_diff_path current_diff_header current_diff_side
547547 global ui_diff ui_index file_states
548548
549+ set selected [$ui_diff tag nextrange sel 0.0]
550+
551+ if {$selected == {}} {
552+ set first [$ui_diff index " @$x ,$y " ]
553+ set last $first
554+ } else {
555+ set first [lindex $selected 0]
556+ set last [lindex $selected 1]
557+ }
558+
559+ set first_l [$ui_diff index " $first linestart" ]
560+ set last_l [$ui_diff index " $last lineend" ]
561+
549562 if {$current_diff_path eq {} || $current_diff_header eq {}} return
550563 if {![lock_index apply_hunk]} return
551564
@@ -568,120 +581,147 @@ proc apply_line {x y} {
568581 }
569582 }
570583
571- set the_l [ $ui_diff index @ $x , $y ]
584+ set wholepatch {}
572585
573- # operate only on change lines
574- set c1 [$ui_diff get " $the_l linestart" ]
575- if {$c1 ne {+} && $c1 ne {-}} {
576- unlock_index
577- return
578- }
579- set sign $c1
580-
581- set i_l [$ui_diff search -backwards -regexp ^@@ $the_l 0.0]
582- if {$i_l eq {}} {
583- unlock_index
584- return
585- }
586- # $i_l is now at the beginning of a line
586+ while {$first_l < $last_l } {
587+ set i_l [$ui_diff search -backwards -regexp ^@@ $first_l 0.0]
588+ if {$i_l eq {}} {
589+ # If there's not a @@ above, then the selected range
590+ # must have come before the first_l @@
591+ set i_l [$ui_diff search -regexp ^@@ $first_l $last_l ]
592+ }
593+ if {$i_l eq {}} {
594+ unlock_index
595+ return
596+ }
597+ # $i_l is now at the beginning of a line
587598
588- # pick start line number from hunk header
589- set hh [$ui_diff get $i_l " $i_l + 1 lines" ]
590- set hh [lindex [split $hh ,] 0]
591- set hln [lindex [split $hh -] 1]
599+ # pick start line number from hunk header
600+ set hh [$ui_diff get $i_l " $i_l + 1 lines" ]
601+ set hh [lindex [split $hh ,] 0]
602+ set hln [lindex [split $hh -] 1]
592603
593- # There is a special situation to take care of. Consider this hunk:
594- #
595- # @@ -10,4 +10,4 @@
596- # context before
597- # -old 1
598- # -old 2
599- # +new 1
600- # +new 2
601- # context after
602- #
603- # We used to keep the context lines in the order they appear in the
604- # hunk. But then it is not possible to correctly stage only
605- # "-old 1" and "+new 1" - it would result in this staged text:
606- #
607- # context before
608- # old 2
609- # new 1
610- # context after
611- #
612- # (By symmetry it is not possible to *un*stage "old 2" and "new 2".)
613- #
614- # We resolve the problem by introducing an asymmetry, namely, when
615- # a "+" line is *staged*, it is moved in front of the context lines
616- # that are generated from the "-" lines that are immediately before
617- # the "+" block. That is, we construct this patch:
618- #
619- # @@ -10,4 +10,5 @@
620- # context before
621- # +new 1
622- # old 1
623- # old 2
624- # context after
625- #
626- # But we do *not* treat "-" lines that are *un*staged in a special
627- # way.
628- #
629- # With this asymmetry it is possible to stage the change
630- # "old 1" -> "new 1" directly, and to stage the change
631- # "old 2" -> "new 2" by first staging the entire hunk and
632- # then unstaging the change "old 1" -> "new 1".
633-
634- # This is non-empty if and only if we are _staging_ changes;
635- # then it accumulates the consecutive "-" lines (after converting
636- # them to context lines) in order to be moved after the "+" change
637- # line.
638- set pre_context {}
639-
640- set n 0
641- set i_l [$ui_diff index " $i_l + 1 lines" ]
642- set patch {}
643- while {[$ui_diff compare $i_l < " end - 1 chars" ] &&
644- [$ui_diff get $i_l " $i_l + 2 chars" ] ne {@@}} {
645- set next_l [$ui_diff index " $i_l + 1 lines" ]
646- set c1 [$ui_diff get $i_l ]
647- if {[$ui_diff compare $i_l <= $the_l ] &&
648- [$ui_diff compare $the_l < $next_l ]} {
649- # the line to stage/unstage
650- set ln [$ui_diff get $i_l $next_l ]
651- if {$c1 eq {-}} {
652- set n [expr $n +1]
604+ # There is a special situation to take care of. Consider this
605+ # hunk:
606+ #
607+ # @@ -10,4 +10,4 @@
608+ # context before
609+ # -old 1
610+ # -old 2
611+ # +new 1
612+ # +new 2
613+ # context after
614+ #
615+ # We used to keep the context lines in the order they appear in
616+ # the hunk. But then it is not possible to correctly stage only
617+ # "-old 1" and "+new 1" - it would result in this staged text:
618+ #
619+ # context before
620+ # old 2
621+ # new 1
622+ # context after
623+ #
624+ # (By symmetry it is not possible to *un*stage "old 2" and "new
625+ # 2".)
626+ #
627+ # We resolve the problem by introducing an asymmetry, namely,
628+ # when a "+" line is *staged*, it is moved in front of the
629+ # context lines that are generated from the "-" lines that are
630+ # immediately before the "+" block. That is, we construct this
631+ # patch:
632+ #
633+ # @@ -10,4 +10,5 @@
634+ # context before
635+ # +new 1
636+ # old 1
637+ # old 2
638+ # context after
639+ #
640+ # But we do *not* treat "-" lines that are *un*staged in a
641+ # special way.
642+ #
643+ # With this asymmetry it is possible to stage the change "old
644+ # 1" -> "new 1" directly, and to stage the change "old 2" ->
645+ # "new 2" by first staging the entire hunk and then unstaging
646+ # the change "old 1" -> "new 1".
647+ #
648+ # Applying multiple lines adds complexity to the special
649+ # situation. The pre_context must be moved after the entire
650+ # first block of consecutive staged "+" lines, so that
651+ # staging both additions gives the following patch:
652+ #
653+ # @@ -10,4 +10,6 @@
654+ # context before
655+ # +new 1
656+ # +new 2
657+ # old 1
658+ # old 2
659+ # context after
660+
661+ # This is non-empty if and only if we are _staging_ changes;
662+ # then it accumulates the consecutive "-" lines (after
663+ # converting them to context lines) in order to be moved after
664+ # "+" change lines.
665+ set pre_context {}
666+
667+ set n 0
668+ set m 0
669+ set i_l [$ui_diff index " $i_l + 1 lines" ]
670+ set patch {}
671+ while {[$ui_diff compare $i_l < " end - 1 chars" ] &&
672+ [$ui_diff get $i_l " $i_l + 2 chars" ] ne {@@}} {
673+ set next_l [$ui_diff index " $i_l + 1 lines" ]
674+ set c1 [$ui_diff get $i_l ]
675+ if {[$ui_diff compare $first_l <= $i_l ] &&
676+ [$ui_diff compare $i_l < $last_l ] &&
677+ ($c1 eq {-} || $c1 eq {+})} {
678+ # a line to stage/unstage
679+ set ln [$ui_diff get $i_l $next_l ]
680+ if {$c1 eq {-}} {
681+ set n [expr $n +1]
682+ set patch " $patch$pre_context$ln "
683+ set pre_context {}
684+ } else {
685+ set m [expr $m +1]
686+ set patch " $patch$ln "
687+ }
688+ } elseif {$c1 ne {-} && $c1 ne {+}} {
689+ # context line
690+ set ln [$ui_diff get $i_l $next_l ]
653691 set patch " $patch$pre_context$ln "
692+ set n [expr $n +1]
693+ set m [expr $m +1]
694+ set pre_context {}
695+ } elseif {$c1 eq $to_context } {
696+ # turn change line into context line
697+ set ln [$ui_diff get " $i_l + 1 chars" $next_l ]
698+ if {$c1 eq {-}} {
699+ set pre_context " $pre_context $ln "
700+ } else {
701+ set patch " $patch $ln "
702+ }
703+ set n [expr $n +1]
704+ set m [expr $m +1]
654705 } else {
655- set patch " $patch$ln$pre_context "
656- }
657- set pre_context {}
658- } elseif {$c1 ne {-} && $c1 ne {+}} {
659- # context line
660- set ln [$ui_diff get $i_l $next_l ]
661- set patch " $patch$pre_context$ln "
662- set n [expr $n +1]
663- set pre_context {}
664- } elseif {$c1 eq $to_context } {
665- # turn change line into context line
666- set ln [$ui_diff get " $i_l + 1 chars" $next_l ]
667- if {$c1 eq {-}} {
668- set pre_context " $pre_context $ln "
669- } else {
670- set patch " $patch $ln "
706+ # a change in the opposite direction of
707+ # to_context which is outside the range of
708+ # lines to apply.
709+ set patch " $patch$pre_context "
710+ set pre_context {}
671711 }
672- set n [ expr $n +1]
712+ set i_l $next_l
673713 }
674- set i_l $next_l
714+ set patch " $patch$pre_context "
715+ set wholepatch " $wholepatch @@ -$hln ,$n +$hln ,$m @@\n $patch "
716+ set first_l [$ui_diff index " $next_l + 1 lines" ]
675717 }
676- set patch " $patch$pre_context "
677- set patch " @@ -$hln ,$n +$hln ,[ eval expr $n $sign 1] @@\n $patch "
678718
679719 if {[catch {
680720 set enc [get_path_encoding $current_diff_path ]
681721 set p [eval git_write $apply_cmd ]
682722 fconfigure $p -translation binary -encoding $enc
683723 puts -nonewline $p $current_diff_header
684- puts -nonewline $p $patch
724+ puts -nonewline $p $wholepatch
685725 close $p } err]} {
686726 error_popup [append $failed_msg " \n\n $err " ]
687727 }
0 commit comments