From 240c10470024c38d2f6b9ed78c2a4e8ae09edbb1 Mon Sep 17 00:00:00 2001 From: Bert Wesarg Date: Sat, 13 Apr 2013 16:57:58 +0200 Subject: [PATCH] git-gui: add file browser tab Signed-off-by: Bert Wesarg --- git-gui.sh | 10 ++ lib/full_browser.tcl | 495 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 505 insertions(+) create mode 100644 lib/full_browser.tcl diff --git a/git-gui.sh b/git-gui.sh index 571185d..6fed4cc 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2393,14 +2393,17 @@ proc do_quit {{rc {1}}} { proc do_rescan {} { rescan ui_ready + $::browser_tab reload } proc ui_do_rescan {} { rescan {force_first_diff ui_ready} + $::browser_tab reload } proc do_commit {} { commit_tree + $::browser_tab reload } proc next_diff {{after {}}} { @@ -4219,6 +4222,13 @@ foreach i $ui_diff_columns { bind $i {open_from_diff_view %x %y 1} } +# -- Browser Tab +# +${NS}::frame .nb.browser +set ::browser_tab [::full_browser::embed .nb.browser] +$::browser_tab reorder_bindtags +.nb add .nb.browser -text [mc "Browse"] + # -- Status Bar # set main_status [::status_bar::new .status] diff --git a/lib/full_browser.tcl b/lib/full_browser.tcl new file mode 100644 index 0000000..e0651b3 --- /dev/null +++ b/lib/full_browser.tcl @@ -0,0 +1,495 @@ +class full_browser { + +image create photo ::full_browser::img_rblob -data {R0lGODlhEAAQAIUAAPwCBFxaXNze3Ly2rJSWjPz+/Ozq7GxqbJyanPT29HRydMzOzDQyNIyKjERCROTi3Pz69PTy7Pzy7PTu5Ozm3LyqlJyWlJSSjJSOhOzi1LyulPz27PTq3PTm1OzezLyqjIyKhJSKfOzaxPz29OzizLyidIyGdIyCdOTOpLymhOzavOTStMTCtMS+rMS6pMSynMSulLyedAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAQABAAAAaQQIAQECgajcNkQMBkDgKEQFK4LFgLhkMBIVUKroWEYlEgMLxbBKLQUBwc52HgAQ4LBo049atWQyIPA3pEdFcQEhMUFYNVagQWFxgZGoxfYRsTHB0eH5UJCJAYICEinUoPIxIcHCQkIiIllQYEGCEhJicoKYwPmiQeKisrKLFKLCwtLi8wHyUlMYwM0tPUDH5BACH+aENyZWF0ZWQgYnkgQk1QVG9HSUYgUHJvIHZlcnNpb24gMi41DQqpIERldmVsQ29yIDE5OTcsMTk5OC4gQWxsIHJpZ2h0cyByZXNlcnZlZC4NCmh0dHA6Ly93d3cuZGV2ZWxjb3IuY29tADs=} +image create photo ::full_browser::img_xblob -data {R0lGODlhEAAQAIYAAPwCBFRWVFxaXNza3OTi3Nze3Ly2tJyanPz+/Ozq7GxubNzSxMzOzMTGxHRybDQyNLy+vHRydHx6fKSipISChIyKjGxqbERCRCwuLLy6vGRiZExKTCQiJAwKDLSytLy2rJSSlHx+fDw6PKyqrBQWFPTu5Ozm3LyulLS2tCQmJAQCBPTq3Ozi1MSynCwqLAQGBOTazOzizOzezLyqjBweHNzSvOzaxKyurHRuZNzOtLymhDw+PIyCdOzWvOTOpLyidNzKtOTStLyifMTCtMS+rLyedAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAQABAAAAfZgACCAAEChYeGg4oCAwQFjgYBBwGKggEECJkICQoIkwADCwwNDY2mDA4Lng8QDhESsLARExQVDhYXGBkWExIaGw8cHR4SCQQfFQ8eFgUgIQEiwiMSBMYfGB4atwEXDyQd0wQlJicPKAHoFyIpJCoeDgMrLC0YKBsX6i4kL+4OMDEyZijr5oLGNxUqUCioEcPGDAwjPNyI6MEDChQjcOSwsUDHgw07RIgI4KCkAgs8cvTw8eOBogAxQtXIASTISiEuBwUYMoRIixYnZggpUgTDywdIkWJIitRPIAAh/mhDcmVhdGVkIGJ5IEJNUFRvR0lGIFBybyB2ZXJzaW9uIDIuNQ0KqSBEZXZlbENvciAxOTk3LDE5OTguIEFsbCByaWdodHMgcmVzZXJ2ZWQuDQpodHRwOi8vd3d3LmRldmVsY29yLmNvbQA7} +image create photo ::full_browser::img_tree -data {R0lGODlhEAAQAIYAAPwCBAQCBExKTBwWHMzKzOzq7ERCRExGTCwqLARqnAQ+ZHR2dKyqrNTOzHx2fCQiJMTi9NTu9HzC3AxmnAQ+XPTm7Dy67DymzITC3IzG5AxypHRydKymrMzOzOzu7BweHByy9AyGtFyy1IzG3NTu/ARupFRSVByazBR6rAyGvFyuzJTK3MTm9BR+tAxWhHS61MTi7Pz+/IymvCxulBRelAx2rHS63Pz6/PTy9PTu9Nza3ISitBRupFSixNTS1CxqnDQyNMzGzOTi5MTCxMTGxGxubGxqbLy2vLSutGRiZLy6vLSytKyurDQuNFxaXKSipDw6PAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAQABAAAAfDgACCAAECg4eIAAMEBQYHCImDBgkKCwwNBQIBBw4Bhw8QERITFJYEFQUFnoIPFhcYoRkaFBscHR4Ggh8gIRciEiMQJBkltCa6JyUoKSkXKhIrLCQYuQAPLS4TEyUhKb0qLzDVAjEFMjMuNBMoNcw21QY3ODkFOjs82RM1PfDzFRU3fOggcM7Fj2pAgggRokOHDx9DhhAZUqQaISBGhjwMEvEIkiIHEgUAkgSJkiNLmFSMJChAEydPGBSBwvJQgAc0/QQCACH+aENyZWF0ZWQgYnkgQk1QVG9HSUYgUHJvIHZlcnNpb24gMi41DQqpIERldmVsQ29yIDE5OTcsMTk5OC4gQWxsIHJpZ2h0cyByZXNlcnZlZC4NCmh0dHA6Ly93d3cuZGV2ZWxjb3IuY29tADs=} +image create photo ::full_browser::img_symlink -data {R0lGODlhEAAQAIQAAPwCBCwqLLSytLy+vERGRFRWVDQ2NKSmpAQCBKyurMTGxISChJyanHR2dIyKjGxubHRydGRmZIyOjFxeXHx6fAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAQABAAAAVbICACwWieY1CibCCsrBkMb0zchSEcNYskCtqBBzshFkOGQFk0IRqOxqPBODRHCMhCQKteRc9FI/KQWGOIyFYgkDC+gPR4snCcfRGKOIKIgSMQE31+f4OEYCZ+IQAh/mhDcmVhdGVkIGJ5IEJNUFRvR0lGIFBybyB2ZXJzaW9uIDIuNQ0KqSBEZXZlbENvciAxOTk3LDE5OTguIEFsbCByaWdodHMgcmVzZXJ2ZWQuDQpodHRwOi8vd3d3LmRldmVsY29yLmNvbQA7} +image create photo ::full_browser::img_unknown -data {R0lGODlhEAAQAIUAAPwCBFxaXIyKjNTW1Nze3LS2tJyanER2RGS+VPz+/PTu5GxqbPz69BQ6BCxeLFSqRPT29HRydMzOzDQyNERmPKSypCRWHIyKhERCRDyGPKz2nESiLBxGHCyCHGxubPz6/PTy7Ozi1Ly2rKSipOzm3LyqlKSWhCRyFOzizLymhNTKtNzOvOzaxOTStPz27OzWvOTOpLSupLyedMS+rMS6pMSulLyqjLymfLyifAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAQABAAAAamQIAQECgajcOkYEBoDgoBQyAJOCCuiENCsWBIh9aGw9F4HCARiXciRDQoBUnlYRlcIgsMG5CxXAgMGhscBRAEBRd7AB0eBBoIgxUfICEiikSPgyMMIAokJZcBkBybJgomIaBJAZoMpyCmqkMBFCcVCrgKKAwpoSorKqchKCwtvasIFBIhLiYvLzDHsxQNMcMKLDAwMqEz3jQ1NTY3ONyrE+jp6hN+QQAh/mhDcmVhdGVkIGJ5IEJNUFRvR0lGIFBybyB2ZXJzaW9uIDIuNQ0KqSBEZXZlbENvciAxOTk3LDE5OTguIEFsbCByaWdodHMgcmVzZXJ2ZWQuDQpodHRwOi8vd3d3LmRldmVsY29yLmNvbQA7} +image create photo ::full_browser::img_find -data {R0lGODlhEAAQAIYAAPwCBCQmJDw+PBQSFAQCBMza3NTm5MTW1HyChOT29Ozq7MTq7Kze5Kzm7Oz6/NTy9Iza5GzGzKzS1Nzy9Nz29Kzq9HTGzHTK1Lza3AwKDLzu9JTi7HTW5GTCzITO1Mzq7Hza5FTK1ESyvHzKzKzW3DQyNDyqtDw6PIzW5HzGzAT+/Dw+RKyurNTOzMTGxMS+tJSGdATCxHRydLSqpLymnLSijBweHERCRNze3Pz69PTy9Oze1OTSxOTGrMSqlLy+vPTu5OzSvMymjNTGvNS+tMy2pMyunMSefAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAQABAAAAe4gACCAAECA4OIiAIEBQYHBAKJgwIICQoLDA0IkZIECQ4PCxARCwSSAxITFA8VEBYXGBmJAQYLGhUbHB0eH7KIGRIMEBAgISIjJKaIJQQLFxERIialkieUGigpKRoIBCqJKyyLBwvJAioEyoICLS4v6QQwMQQyLuqLli8zNDU2BCf1lN3AkUPHDh49fAQAAEnGD1MCCALZEaSHkIUMBQS8wWMIkSJGhBzBmFEGgRsBUqpMiSgdAD+BAAAh/mhDcmVhdGVkIGJ5IEJNUFRvR0lGIFBybyB2ZXJzaW9uIDIuNQ0KqSBEZXZlbENvciAxOTk3LDE5OTguIEFsbCByaWdodHMgcmVzZXJ2ZWQuDQpodHRwOi8vd3d3LmRldmVsY29yLmNvbQA7} + +field w +field w_files +field w_filter + +field filter {} +field filter_show_dot 0 +field files +field matches +field busy 0 + +field matchinfo_str +field matchinfo_str_len +field matchinfo_pattern +field matchinfo_pattern_len +field matchinfo_max_score_per_char +field matchinfo_dot_file +field matchinfo_always_show_dot_files +field matchinfo_never_show_dot_files + +field ls_buf {} + +constructor embed {i_w} { + set w $i_w + + _init $this + + return $this +} + +method _init {} { + global cursor_ptr M1B use_ttk NS + + set w_files $w.list + text $w_files \ + -background white \ + -foreground black \ + -borderwidth 0 \ + -cursor $cursor_ptr \ + -state disabled \ + -wrap none \ + -height 10 \ + -width 70 \ + -xscrollcommand [list $w.sbx set] \ + -yscrollcommand [list $w.sby set] + rmsel_tag $w_files + ${NS}::scrollbar $w.sbx -orient h -command [list $w_files xview] + ${NS}::scrollbar $w.sby -orient v -command [list $w_files yview] + + ${NS}::frame $w.filter + set w_filter $w.filter.e + ${NS}::entry $w_filter \ + -textvariable @filter \ + -validate key \ + -validatecommand [cb _filter_update %P] + if {!$use_ttk} {$w_filter configure -borderwidth 1 -relief sunken} + ${NS}::label $w.filter.i -image ::full_browser::img_find + ${NS}::button $w.filter.x \ + -text "|<" \ + -command [cb _filter_reset] + ${NS}::checkbutton $w.filter.c \ + -text "hidden" \ + -variable @filter_show_dot + + pack $w.filter.c -side right + pack $w.filter.x -side right + pack $w.filter.i -side left + pack $w_filter -side left -fill x -expand 1 + + pack $w.filter -anchor w -side top -fill x + pack $w.sbx -side bottom -fill x + pack $w.sby -side right -fill y + pack $w_files -side bottom -fill both -expand 1 + + bind $w_files "[cb _list_click @%x,%y]; break" + bind $w_files "[cb _list_open @%x,%y]; break" + bind $w_files <$M1B-ButtonRelease-2> "[cb _list_blame @%x,%y]; break" + bind $w_files "[cb _list_move] -1; break" + bind $w_files "[cb _list_move] 1; break" + bind $w_files "[cb _list_page -1]; break" + bind $w_files "[cb _list_page 1]; break" + bind $w_files break + bind $w_files break + bind $w_files "[cb _open_selected]; break" + bind $w_files <$M1B-Return> "[cb _blame_selected]; break" + + bind $w_filter "[cb _filter_reset]; break" + bind $w_filter "[cb _list_move] -1; break" + bind $w_filter "[cb _list_move] 1; break" + bind $w_filter "[cb _list_page -1]; break" + bind $w_filter "[cb _list_page 1]; break" + bind $w_filter "[cb _open_selected]; break" + bind $w_filter <$M1B-Return> "[cb _blame_selected]; break" + bind $w_filter [list focus $w_filter] + + set files [list] + set matches [list] +} + +method reload {} { + set old_pattern $filter + _ls $this + _refresh $this $filter +} + +method reorder_bindtags {} { + foreach i [list $w_filter $w_files] { + bindtags $i [list all $i [winfo class $i] .] + } +} + +method _ls {} { + if {$busy} return + + set ls_buf {} + set files [list] + set busy 1 + + set cmd [list ls-tree -r -z [PARENT]] + set fd [eval git_read $cmd] + fconfigure $fd -blocking 0 -translation binary -encoding binary + fileevent $fd readable [cb _read $fd] +} + +method _read {fd} { + append ls_buf [read $fd] + set pck [split $ls_buf "\0"] + set ls_buf [lindex $pck end] + + foreach p [lrange $pck 0 end-1] { + set tab [string first "\t" $p] + if {$tab == -1} continue + + set info [split [string range $p 0 [expr {$tab - 1}]] { }] + scan [lindex $info 0] %o mode + set type [lindex $info 1] + set object [lindex $info 2] + set path [string range $p [expr {$tab + 1}] end] + set path [encoding convertfrom $path] + + switch -- $type { + blob { + if {$mode == 0120000} { + set image ::full_browser::img_symlink + } elseif {($mode & 0100) != 0} { + set image ::full_browser::img_xblob + } else { + set image ::full_browser::img_rblob + } + } + tree { + set image ::full_browser::img_tree + append path / + } + default { + set image ::full_browser::img_unknown + } + } + + lappend files [list $image $path] + } + + if {[eof $fd]} { + close $fd + set busy 0 + set ls_buf {} + _refresh $this $filter + } +} ifdeleted { + catch {close $fd} +} + +method _compare {a b} { + foreach {a_idx a_score} $a break + foreach {b_idx b_score} $b break + + set ret 0 + if {$a_score > $b_score} { + set ret -1 + } elseif {$a_score < $b_score} { + set ret 1 + } else { + set ret 0 + #foreach {a_image a_path} [lindex $files $a_idx] break + #foreach {b_image b_path} [lindex $files $b_idx] break + #return [string compare $a_path $b_path] + } + #puts "$a_score ? $b_score : $ret" + return $ret +} + +method _refresh {pattern} { + if {$busy} return + + set matches [list] + +# set matchinfo_pattern $pattern +# set matchinfo_pattern_len [string length $matchinfo_pattern] +# set matchinfo_always_show_dot_files 0 +# set matchinfo_never_show_dot_files 0 +# +# set n_files [llength $files] +# for {set idx 0} {$idx < $n_files} {incr idx} { +# foreach {image path} [lindex $files $idx] break +# +# set matchinfo_str $path +# set matchinfo_str_len [string length $matchinfo_str] +# set matchinfo_max_score_per_char [expr [expr [expr 1.0 / $matchinfo_str_len] + [expr 1.0 / $matchinfo_pattern_len]] / 2.0] +# set matchinfo_dot_file 0 +# +# set score 1.0 +# if {$matchinfo_pattern_len == 0} { +# if {!$matchinfo_always_show_dot_files +# && [regexp {^\.|/\.} $matchinfo_str]} { +# set score 0.0 +# } +# } else { +# set score [_recursive_match $this 0 0 0 0.0] +# } +# +# if {$score > 0.0} { +# lappend matches [list $idx $score] +# } +# } +# +# if {$matchinfo_pattern_len != 0 && $matchinfo_pattern ne "."} { +# puts "sort [time {lsort -command [cb _compare] $matches}] [llength $matches]" +# set matches [lsort -command [cb _compare] $matches] +# } + + + set m_pattern {} + set sep {} + set subs [split $pattern {/}] + for {set i 0} {$i < [llength $subs]} {} { + set p [lindex $subs $i] + append m_pattern $sep + incr i + if {$i == [llength $subs]} { + append m_pattern "*" + } + append m_pattern $p + append m_pattern "*" + set sep {/} + } + + set n_files [llength $files] + for {set idx 0} {$idx < $n_files} {incr idx} { + foreach {image path} [lindex $files $idx] break + + if {[string length $pattern] == 0 || [string match -nocase $m_pattern $path]} { + lappend matches [list $idx 0] + } + } + + $w_files conf -state normal + $w_files tag remove in_sel 0.0 end + $w_files delete 0.0 end + + set n 0 + foreach entry $matches { + foreach {idx score} $entry break + foreach {image path} [lindex $files $idx] break + if {$n > 0} { + $w_files insert end "\n" + } + $w_files image create end \ + -align center -padx 5 -pady 1 \ + -name icon[incr n] \ + -image $image + $w_files insert end "[escape_path $path]" + } + $w_files conf -state disabled + + if {$n > 0} { + $w_files tag add in_sel 1.0 2.0 + } +} + +# -- Command-T matcher +# + +method _recursive_match {str_idx abbrev_idx last_idx score} { + + #puts recursive_match + + set seen_score 0 + set dot_file_match 0 + set dot_search 0 + + for {set i $abbrev_idx} {$i < $matchinfo_pattern_len} {incr i} { + set c [string index $matchinfo_pattern $i] + if {$c eq "."} { + set dot_search 1 + } + + set found 0 + for {set j $str_idx} {$j < $matchinfo_str_len} {incr j; incr str_idx} { + set d [string index $matchinfo_str $j] + set last {} + if {$j > 0} { + set last [string index $matchinfo_str [expr $j - 1]] + } + if {$d eq "."} { + if {$j == 0 || $last == "/"} { + set matchinfo_dot_file 1 + if {$dot_search} { + set dot_file_match 1 + } + } + } + set d [string tolower $d] + if {$c == $d} { + set found 1 + set dot_search 0 + + set score_for_char $matchinfo_max_score_per_char + set distance [expr $j - $last_idx] + if {$distance > 1} { + set factor 1.0 + set curr [string index $matchinfo_str $j] ;# case matters, so get again + if {$last == "/"} { + set factor 0.9 + } elseif {[string match {[-_ 0-9]} $last]} { + set factor 0.8 + } elseif {[string match {[a-z]} $last] && [string match {[A-Z]} $curr]} { + set factor 0.8 + } elseif {$last == "."} { + set factor 0.7 + } else { + set factor [expr [expr 1.0 / $distance] * 0.75] + } + set score_for_char [expr $score_for_char * $factor] + } + + incr j + if {$j < $matchinfo_str_len} { + set sub_score [_recursive_match $this $j $i $last_idx $score] + if {$sub_score > $seen_score} { + set seen_score $sub_score + } + } + + set score [expr $score + $score_for_char] + set last_idx $str_idx + incr str_idx + break + } + } + if {!$found} { + return 0.0 + } + } + + if {$matchinfo_dot_file} { + if {$matchinfo_never_show_dot_files || + (!$dot_file_match && !$matchinfo_always_show_dot_files)} { + return 0.0 + } + } + + if {$score > $seen_score} { + return $score + } else { + return $seen_score + } +} + +method _filter_update {P} { + if {$busy} {return 0} + + if {[regexp {\s} $P]} { + return 0 + } + + _refresh $this $P + return 1 +} + +method _filter_reset {} { + $w_filter delete 0 end + _refresh $this $filter +} + +method _list_move {dir} { + if {$busy} return + + if {[catch {$w_files index in_sel.first}]} { + return + } + + set lno [lindex [split [$w_files index in_sel.first] .] 0] + incr lno $dir + + if {[lindex $matches [expr {$lno - 1}]] ne {}} { + $w_files tag remove in_sel 0.0 end + $w_files tag add in_sel $lno.0 "$lno.0 + 1 line" + $w_files see $lno.0 + } +} + +method _list_page {dir} { + if {$busy} return + + $w_files yview scroll $dir pages + set lno [expr {int( + [lindex [$w_files yview] 0] + * [llength $matches] + + 1)}] + if {[lindex $matches [expr {$lno - 1}]] ne {}} { + $w_files tag remove in_sel 0.0 end + $w_files tag add in_sel $lno.0 [expr {$lno + 1}].0 + $w_files see $lno.0 + } +} + +method _list_click {pos} { + if {$busy} return + + set lno [lindex [split [$w_files index $pos] .] 0] + focus $w_files + + if {[lindex $matches [expr {$lno - 1}]] ne {}} { + $w_files tag remove in_sel 0.0 end + $w_files tag add in_sel $lno.0 [expr {$lno + 1}].0 + } +} + +method _list_open {pos} { + if {$busy} return + + # select the file + _list_click $this $pos + + set lno [lindex [split [$w_files index $pos] .] 0] + + set filename [lindex [lindex $files [lindex [lindex $matches [expr {$lno - 1}] 0]]] 1] + if {$filename ne {}} { + open_in_git_editor $filename + } +} + +method _list_blame {pos} { + if {$busy} return + + # select the file + #_list_click $this $pos + + set lno [lindex [split [$w_files index $pos] .] 0] + + set filename [lindex [lindex $files [lindex [lindex $matches [expr {$lno - 1}] 0]]] 1] + if {$filename ne {}} { + blame_path_in_tab $filename + } +} + +method _open_selected {} { + if {$busy} return + + if {[catch {$w_files index in_sel.first}]} { + return + } + + set lno [lindex [split [$w_files index in_sel.first] .] 0] + + set filename [lindex [lindex $files [lindex [lindex $matches [expr {$lno - 1}] 0]]] 1] + if {$filename ne {}} { + open_in_git_editor $filename + } +} + +method _blame_selected {} { + if {$busy} return + + if {[catch {$w_files index in_sel.first}]} { + return + } + + set lno [lindex [split [$w_files index in_sel.first] .] 0] + + set filename [lindex [lindex $files [lindex [lindex $matches [expr {$lno - 1}] 0]]] 1] + if {$filename ne {}} { + blame_path_in_tab $filename + } +} + +} -- 2.11.4.GIT