16 field ui_cnts_width_max
19 field ui_lnos_width_max
24 field previous_path
{}
25 field current_path_label
{}
31 field first_match_line
0
35 constructor embed
{i_w
} {
43 constructor new
{args
} {
46 wm title
$top "Git-Gui: Grep"
50 bind $top <Control-Key-r
> [cb grep
]
51 bind $top <Control-Key-R
> [cb grep
]
52 bind $top <Control-Key-h
> [cb grep_from_selection
]
53 bind $top <Control-Key-H
> [cb grep_from_selection
]
55 set font_w
[font measure font_diff
"0"]
57 set req_w
[winfo reqwidth
$top]
58 set req_h
[winfo reqheight
$top]
59 set scr_w
[expr {[winfo screenwidth
$top] - 40}]
60 set scr_h
[expr {[winfo screenheight
$top] - 120}]
61 set opt_w
[expr {$font_w * (80 + 32)}]
62 if {$req_w < $opt_w} {set req_w
$opt_w}
63 if {$req_w > $scr_w} {set req_w
$scr_w}
64 set opt_h
[expr {$scr_h*1/2}]
65 if {$req_h < $scr_h} {set req_h
$scr_h}
66 if {$req_h > $opt_h} {set req_h
$opt_h}
67 set g
"${req_w}x${req_h}"
71 wm protocol
$top WM_DELETE_WINDOW
"destroy $top"
72 bind $top <Destroy
> [cb _handle_destroy
%W
]
74 if {[llength $args] > 0} {
80 if {[regexp {[ \t\r\n'
"$?*]} $arg]} {
92 # base path for all grep widgets
95 set w_files
$w_vpane.f.
text
96 set w_cnts
$w_vpane.f.cnts
97 set w_grep
$w_vpane.o.
text
98 set w_lnos
$w_vpane.o.lnos
100 ttk
::panedwindow $w_vpane -orient horizontal
103 -font TkDefaultFont
\
104 -disabledforeground white
\
105 -disabledbackground blue
\
107 -validatecommand [cb _reset_errstatus
] \
108 -takefocus [cb _always_takefocus
]
110 pack $w_vpane -side top
-fill both
-expand 1
111 pack $w_entry -side bottom
-fill x
113 ttk
::frame $w_vpane.f
-borderwidth 0
114 ttk
::frame $w_vpane.o
-borderwidth 0
115 $w_vpane add
$w_vpane.f
-weight 0
116 $w_vpane add
$w_vpane.o
-weight 1
118 ## list of files with matches
120 ttk
::label $w_vpane.f.title
\
121 -style Color.TLabel
\
122 -text "Matched Files" \
123 -background lightsalmon
\
131 -highlightthickness 0 \
137 -xscrollcommand [list $w_vpane.f.sbx
set]
138 $w_files tag conf
default -lmargin1 5 -rmargin 1
142 -highlightthickness 0 \
147 -width [expr $ui_cnts_width + 1] \
151 $w_cnts tag conf count
-justify right
-lmargin1 2 -rmargin 3 -foreground red
153 set ui_files_cols
[list $w_files $w_cnts]
155 # simulate linespacing, as if it has an icon like the index/worktree
157 set fn
[$w_cnts cget
-font]
158 set ls
[font metrics
$fn -linespace]
160 set d
[expr 17 - $ls]
163 if {[expr $b + $t] != $d} {
166 foreach i
$ui_files_cols {
167 $i configure
-spacing1 $t -spacing3 $b
171 ttk
::scrollbar $w_vpane.f.sbx
\
173 -command [list $w_files xview
]
175 ttk
::scrollbar $w_vpane.f.sby
\
177 -command [list scrollbar2many
$ui_files_cols yview
]
179 grid configure
$w_vpane.f.title
\
184 grid $w_files $w_cnts $w_vpane.f.sby
-sticky nsew
186 grid configure
$w_vpane.f.sbx
\
191 grid columnconfigure
$w_vpane.f
\
194 grid rowconfigure
$w_vpane.f
\
198 foreach i
$ui_files_cols {
201 $i conf
-cursor arrow
202 $i conf
-yscrollcommand \
203 "[list many2scrollbar $ui_files_cols yview $w_vpane.f.sby]"
205 bind $i <Button-1
> "[cb _select_from_list %x %y]; break"
206 bind $i <ButtonRelease-2
> "[cb _select_from_list %x %y [cb _open_first_match]]; break"
209 ## grep output from one file
211 ttk
::label $w_vpane.o.title
\
212 -style Color.TLabel
\
213 -textvariable @current_path_label
\
219 set ctxm
$w_vpane.o.title.ctxm
220 menu $ctxm -tearoff 0
223 -command [cb _copy_path
]
224 bind_button3
$w_vpane.o.title
"tk_popup $ctxm %X %Y"
228 -highlightthickness 0 \
233 -width [expr $ui_lnos_width + 1] \
238 $w_lnos tag conf linenumber
-justify right
-rmargin 5
242 -highlightthickness 0 \
250 -xscrollcommand [list $w_vpane.o.sbx
set] \
254 $w_grep tag conf hunksep
-background grey95
255 $w_grep tag conf d_info
-foreground blue
-font font_diffbold
257 foreach {n c
} {0 black
1 red
2 green4
3 yellow4
4 blue4
5 magenta4
6 cyan4
7 grey60
} {
258 $w_grep tag configure clr4
$n -background $c
259 $w_grep tag configure clri4
$n -foreground $c
260 $w_grep tag configure clr3
$n -foreground $c
261 $w_grep tag configure clri3
$n -background $c
263 $w_grep tag configure clr1
-font font_diffbold
264 $w_grep tag configure clr4
-underline 1
266 set ui_grep_cols
[list $w_lnos $w_grep]
268 delegate_sel_to
$w_grep [list $w_lnos]
270 ttk
::scrollbar $w_vpane.o.sbx
\
272 -command [list $w_grep xview
]
274 ttk
::scrollbar $w_vpane.o.sby
\
276 -command [list scrollbar2many
$ui_grep_cols yview
]
278 grid configure
$w_vpane.o.title
\
283 grid $w_lnos $w_grep $w_vpane.o.sby
-sticky nsew
285 grid configure
$w_vpane.o.sbx
\
290 grid columnconfigure
$w_vpane.o
\
293 grid rowconfigure
$w_vpane.o
\
298 foreach i
$ui_grep_cols {
301 $i conf
-yscrollcommand \
302 "[list many2scrollbar $ui_grep_cols yview $w_vpane.o.sby]"
304 bind $i <ButtonRelease-2
> "[cb _open_from_grep %x %y]"
307 foreach i
[list $w $w_files $w_cnts $w_lnos $w_grep $w_entry] {
310 bind $i <Alt-Key-Left
> "[cb grep_prev]; break"
311 bind $i <Alt-Key-Right
> "[cb grep_next]; break"
314 bind $i <Up
> "[cb _files_scroll_line -1]; break"
315 bind $i <Down
> "[cb _files_scroll_line 1]; break"
316 bind $i <Key-Prior
> "[cb _files_scroll_page -1]; break"
317 bind $i <Key-Next
> "[cb _files_scroll_page 1]; break"
319 # scroll of grep result
320 bind $i <Alt-Key-Up
> "[cb _grep_scroll yview -1 units]; break"
321 bind $i <Alt-Key-Down
> "[cb _grep_scroll yview 1 units]; break"
322 bind $i <Alt-Prior
> "[cb _grep_scroll yview -1 pages]; break"
323 bind $i <Alt-Next
> "[cb _grep_scroll yview 1 pages]; break"
326 foreach i
[list $w $w_files $w_cnts $w_lnos $w_grep] {
328 bind $i <Right
> break
329 bind $i <Return
> "[cb _open_first_match]; break"
332 bind $w_entry <Return
> [cb _grep_from_entry
]
333 bind $w_entry <Shift-Return
> "[cb _open_first_match]; break"
334 bind $w_entry <Key-Left
> [cb _reset_errstatus
]
335 bind $w_entry <Key-Right
> [cb _reset_errstatus
]
336 bind $w_entry <Control-Key-c
> [cb _cancel
]
337 bind $w_entry <Visibility
> [list focus $w_entry]
339 trace add
variable current_path write
[cb _update_path_label
]
345 method _clear_grep
{} {
346 foreach i
$ui_grep_cols {
347 $i conf
-state normal
349 $i conf
-state disabled
351 $w_lnos conf
-width [expr $ui_lnos_width + 1]
357 set first_match_line
0
359 set previous_path
$current_path
363 method grep
{{pattern
{}}} {
367 $w_entry delete
0 end
370 foreach i
$ui_files_cols {
371 $i conf
-state normal
373 $i conf
-state disabled
375 $w_cnts conf
-width [expr $ui_cnts_width + 1]
376 set ui_cnts_width_max
$ui_cnts_width
382 if {$pattern ne
{}} {
383 lappend patterns
"$pattern"
384 set patterns_pos
[expr [llength $patterns] - 1]
387 if {$patterns_pos == -1} {
392 set pattern
[lindex $patterns $patterns_pos]
394 $w_entry insert
0 $pattern
395 $w_entry conf
-state disabled
397 ui_status
"Grep for matching files..."
398 set cmd
[list |
[shellpath
] -c "git grep -c -z $pattern"]
399 if {[catch {set current_fd
[open $cmd r
]} err
]} {
400 $w_entry conf
-state normal
-background red
406 -title {git-gui
: grep
: fatal
error} \
411 fconfigure $current_fd -eofchar {}
412 fconfigure $current_fd \
417 fileevent $current_fd readable
[cb _do_read
]
421 append buf_rgl
[read $current_fd]
423 set n
[string length
$buf_rgl]
425 foreach i
$ui_files_cols {$i conf
-state normal
}
427 # find the \0 after a path
428 set zb
[string first
"\0" $buf_rgl $c]
430 set path
[string range
$buf_rgl $c [expr {$zb - 1}]]
433 # find the newline after the count
434 set nl
[string first
"\n" $buf_rgl $zb]
436 set cnt
[string range
$buf_rgl $zb [expr {$nl - 1}]]
439 set path
[encoding convertfrom
$path]
440 lappend file_list
$path
442 $w_cnts insert end
"$cnt\n" count
443 set cnt_len
[string length
$cnt]
444 if {$ui_cnts_width_max < $cnt_len} {
445 set ui_cnts_width_max
$cnt_len
447 $w_files insert end
"[escape_path $path]\n" default
451 foreach i
$ui_files_cols {$i conf
-state disabled
}
454 set buf_rgl
[string range
$buf_rgl $c end
]
459 fconfigure $current_fd -blocking 1
460 if {![eof $current_fd]} {
461 fconfigure $current_fd -blocking 0
465 if {[catch {close $current_fd} err
]} {
466 $w_entry conf
-state normal
-background red
468 $w_entry conf
-state normal
-background green
471 # remove trailing newline
472 foreach i
$ui_files_cols {
473 $i conf
-state normal
474 $i delete
"end -1 char"
475 $i conf
-state disabled
477 $w_cnts conf
-width [expr $ui_cnts_width_max + 1]
482 if {[llength $file_list] eq
0} {
487 if {$previous_path ne
{}} {
488 set file_index
[lsearch -exact $file_list $previous_path]
490 if {$file_index == -1} {
493 # lines starting with 1, so add 1 more line to the zero based file_index
494 set line
[expr {$file_index + 1}]
495 foreach i
$ui_files_cols {
496 $i tag add in_sel
"$line.0" "$line.0 + 1 line"
498 _show_file
$this [lindex $file_list $file_index]
499 } ifdeleted
{ catch {close $current_fd} }
501 method _show_file
{path
{after {}}} {
502 set cmd
[list |
[shellpath
] -c "git grep --color -h -n -p -3 [lindex $patterns $patterns_pos] -- [sq [encoding convertto $path]]"]
505 set ui_lnos_width_max
$ui_lnos_width
507 ui_status
"Grepping [escape_path $path]..."
508 set current_path
$path
510 if {[catch {set fd
[open $cmd r
]} err
]} {
514 -title {gui-grep
: fatal
error} \
517 ui_status
"Grepping of [escape_path $path] failed..."
521 fconfigure $fd -eofchar {}
524 -encoding [get_path_encoding
$path] \
528 fileevent $fd readable
[cb _file_read
$fd $path $after]
531 method _file_read
{fd path
after} {
532 foreach i
$ui_grep_cols {$i conf
-state normal
}
534 while {[gets $fd line
] >= 0} {
536 set nl_tag
$next_nl_tag
539 if {[string match
{Binary
file * matches
} $line]} {
540 $w_lnos insert end
"${grep_nl}*" linenumber
541 $w_grep insert end
"${grep_nl}Binary file matches" d_info
548 if {[regexp {^
(?
:\033\[(?
:(?
:\d
+;)*\d
+)m
)?
--(?
:\033\[m
)?
} $line]} {
552 set next_nl_tag hunksep
554 # remove any color from lno and sep
555 regexp {^
(?
:\033\[(?
:(?
:\d
+;)*\d
+)m
)?
(\d
+)(?
:\033\[m
)?
(?
:\033\[(?
:(?
:\d
+;)*\d
+)m
)?
([-:=])(?
:\033\[m
)?
(.
*)$} $line all lno line_type mline
556 foreach {mline markup
} [parse_color_line
$mline] break
557 set mline
[string map
{\033 ^
} $mline]
558 regsub {\r$} $mline {} mline
559 if {$line_type eq
{:} && $first_match_line eq
0} {
560 set first_match_line
$lno
566 $w_lnos insert end
"$grep_nl$lno" linenumber
567 set lno_len
[string length
$lno]
568 if {$ui_lnos_width_max < $lno_len} {
569 set ui_lnos_width_max
$lno_len
571 $w_grep insert end
"$grep_nl" $nl_tag
572 $w_grep insert end
"$mline"
576 foreach {posbegin colbegin posend colend
} $markup {
578 foreach style
[lsort -integer [split $colbegin ";"]] {
579 if {$style eq
"7"} {append prefix i
; continue}
580 # ignore bold (1), because it doesn't buy us anything
582 && ($style < 30 ||
$style > 37)
583 && ($style < 40 ||
$style > 47)} {
586 set a
"$mark + $posbegin chars"
587 set b
"$mark + $posend chars"
588 catch {$w_grep tag add
$prefix$style $a $b}
596 #update line number column width
597 $w_lnos conf
-width [expr $ui_lnos_width_max + 1]
606 foreach i
$ui_grep_cols {$i conf
-state disabled
}
607 } ifdeleted
{ catch {close $fd} }
609 method _select_from_list
{x y
{after {}}} {
612 set lno
[lindex [split [$w_files index
@0,$y] .
] 0]
613 set path
[lindex $file_list [expr {$lno - 1}]]
618 foreach i
$ui_files_cols {
619 $i tag remove in_sel
0.0 end
620 $i tag add in_sel
$lno.0 "$lno.0 + 1 line"
623 if {$path eq
$current_path} {
630 _show_file
$this $path $after
633 method _open_from_grep
{x y
} {
634 if {$current_path eq
{}} {
639 set wlno
[$w_lnos search
-regexp {^
[[:digit
:]]+$} "@0,$y linestart" end
]
641 set wlno
[$w_lnos search
-backwards -regexp {^
[[:digit
:]]+$} "@0,$y linestart" 1.0]
644 set lno
[$w_lnos get
"$wlno" "$wlno lineend"]
647 open_in_git_editor
$current_path $lno
650 method _open_first_match
{} {
651 if {$current_path eq
{} ||
$first_match_line == 0} {
654 open_in_git_editor
$current_path $first_match_line
657 method grep_from_selection
{} {
658 if {[catch {set expr [selection get
-selection PRIMARY
-type STRING
]}]} {
666 grep
$this "-F -e $expr"
669 method _grep_from_entry
{} {
670 set expr [$w_entry get
]
672 # open selected file if we didn't changed the pattern
673 if {$patterns_pos != -1 && $expr eq
[lindex $patterns $patterns_pos]} {
674 _open_first_match
$this
680 method grep_prev
{} {
681 _reset_errstatus
$this
682 if {$patterns_pos > 0} {
688 method grep_next
{} {
689 _reset_errstatus
$this
690 if {[expr {$patterns_pos + 1}] < [llength $patterns]} {
696 method _update_path_label
{args
} {
697 if {$current_path eq
{}} {
698 set current_path_label
"No file matched."
700 set current_path_label
"File: [escape_path $current_path]"
704 method _files_scroll_line
{dir
} {
707 if {[catch {$w_files index in_sel.first
}]} {
711 set lno
[lindex [split [$w_files index in_sel.first
] .
] 0]
714 set path
[lindex $file_list [expr {$lno - 1}]]
719 foreach i
$ui_files_cols {
720 $i tag remove in_sel
0.0 end
721 $i tag add in_sel
$lno.0 "$lno.0 + 1 line"
725 _show_file
$this $path
728 method _files_scroll_page
{dir
} {
731 $w_files yview scroll
$dir pages
733 [lindex [$w_files yview
] 0]
734 * [llength $file_list]
737 set path
[lindex $file_list [expr {$lno - 1}]]
742 foreach i
$ui_files_cols {
743 $i tag remove in_sel
0.0 end
744 $i tag add in_sel
$lno.0 "$lno.0 + 1 line"
748 _show_file
$this $path
751 method _grep_scroll
{v a u
} {
752 $w_grep $v scroll
$a $u
755 method _copy_path
{} {
763 method _reset_errstatus
{} {
764 $w_entry conf
-background white
771 catch {close $current_fd}
772 $w_entry conf
-state normal
-background red
773 foreach i
$ui_files_cols {
774 $i conf
-state normal
775 $i delete
"end -1 char"
776 $i conf
-state disabled
781 method _always_takefocus
{w
} {
785 method _handle_destroy
{win
} {
791 method link_vpane
{vpane
} {
792 bind $w_vpane <Map
> [cb _on_pane_mapped
$vpane]
795 method _on_pane_mapped
{master_vpane
} {
797 after idle
[list after idle
[list $w_vpane sashpos
0 [$master_vpane sashpos
0]]]
799 after idle
[list after idle
\
800 [list $w_vpane sash
place 0 \
801 [lindex [$master_vpane sash coord
0] 0] \
802 [lindex [$w_vpane sash coord
0] 1]]]
806 method reorder_bindtags
{} {
807 foreach i
[list $w $w_files $w_cnts $w_lnos $w_grep $w_entry] {
808 bindtags $i [list all
$i [winfo class
$i] .
]