diff options
| author | Junio C Hamano <gitster@pobox.com> | 2011-07-06 15:37:42 -0700 |
|---|---|---|
| committer | Junio C Hamano <gitster@pobox.com> | 2011-07-06 15:37:42 -0700 |
| commit | 4d9e42f8f11c57b32b976a943c8ddaf6214e64b8 (patch) | |
| tree | f1aee1490288aa30fb62a981696389d9b5e3e992 /git-gui/lib | |
| parent | c5bcf1f9f6d3429ab9a09e07e28362e7d189005b (diff) | |
| parent | ea02eef096d4bfcbb83e76cfab0fcb42dbcad35e (diff) | |
Merge commit 'v1.6.0' into jc/checkout-reflog-fix
* commit 'v1.6.0': (2063 commits)
GIT 1.6.0
git-p4: chdir now properly sets PWD environment variable in msysGit
Improve error output of git-rebase
t9300: replace '!' with test_must_fail
Git.pm: Make File::Spec and File::Temp requirement lazy
Documentation: document the pager.* configuration setting
git-stash: improve synopsis in help and manual page
Makefile: building git in cygwin 1.7.0
git-am: ignore --binary option
bash-completion: Add non-command git help files to bash-completion
Fix t3700 on filesystems which do not support question marks in names
Utilise our new p4_read_pipe and p4_write_pipe wrappers
Add p4 read_pipe and write_pipe wrappers
bash completion: Add '--merge' long option for 'git log'
bash completion: Add completion for 'git mergetool'
git format-patch documentation: clarify what --cover-letter does
bash completion: 'git apply' should use 'fix' not 'strip'
t5304-prune: adjust file mtime based on system time rather than file mtime
test-parse-options: use appropriate cast in length_callback
Fix escaping of glob special characters in pathspecs
...
Conflicts:
builtin-checkout.c
Diffstat (limited to 'git-gui/lib')
| -rw-r--r-- | git-gui/lib/about.tcl | 6 | ||||
| -rw-r--r-- | git-gui/lib/blame.tcl | 130 | ||||
| -rw-r--r-- | git-gui/lib/branch_create.tcl | 3 | ||||
| -rw-r--r-- | git-gui/lib/branch_delete.tcl | 2 | ||||
| -rw-r--r-- | git-gui/lib/browser.tcl | 3 | ||||
| -rw-r--r-- | git-gui/lib/checkout_op.tcl | 30 | ||||
| -rw-r--r-- | git-gui/lib/choose_font.tcl | 2 | ||||
| -rw-r--r-- | git-gui/lib/choose_repository.tcl | 14 | ||||
| -rw-r--r-- | git-gui/lib/choose_rev.tcl | 3 | ||||
| -rw-r--r-- | git-gui/lib/commit.tcl | 161 | ||||
| -rw-r--r-- | git-gui/lib/console.tcl | 10 | ||||
| -rw-r--r-- | git-gui/lib/database.tcl | 4 | ||||
| -rw-r--r-- | git-gui/lib/diff.tcl | 163 | ||||
| -rw-r--r-- | git-gui/lib/error.tcl | 46 | ||||
| -rw-r--r-- | git-gui/lib/index.tcl | 2 | ||||
| -rw-r--r-- | git-gui/lib/merge.tcl | 6 | ||||
| -rw-r--r-- | git-gui/lib/option.tcl | 42 | ||||
| -rw-r--r-- | git-gui/lib/spellcheck.tcl | 414 |
18 files changed, 919 insertions, 122 deletions
diff --git a/git-gui/lib/about.tcl b/git-gui/lib/about.tcl index 719fc547b3..241ab892cd 100644 --- a/git-gui/lib/about.tcl +++ b/git-gui/lib/about.tcl @@ -4,6 +4,7 @@ proc do_about {} { global appvers copyright oguilib global tcl_patchLevel tk_patchLevel + global ui_comm_spell set w .about_dialog toplevel $w @@ -40,6 +41,11 @@ proc do_about {} { append v "Tcl version $tcl_patchLevel" append v ", Tk version $tk_patchLevel" } + if {[info exists ui_comm_spell] + && [$ui_comm_spell version] ne {}} { + append v "\n" + append v [$ui_comm_spell version] + } set d {} append d "git wrapper: $::_git\n" diff --git a/git-gui/lib/blame.tcl b/git-gui/lib/blame.tcl index 00ecf21333..b6e42cbc8f 100644 --- a/git-gui/lib/blame.tcl +++ b/git-gui/lib/blame.tcl @@ -33,13 +33,6 @@ variable group_colors { #ececec } -# Switches for original location detection -# -variable original_options [list -C -C] -if {[git-version >= 1.5.3]} { - lappend original_options -w ; # ignore indentation changes -} - # Current blame data; cleared/reset on each load # field commit ; # input commit to blame @@ -80,6 +73,7 @@ constructor new {i_commit i_path} { label $w.header.commit_l \ -text [mc "Commit:"] \ -background gold \ + -foreground black \ -anchor w \ -justify left set w_back $w.header.commit_b @@ -89,6 +83,7 @@ constructor new {i_commit i_path} { -relief flat \ -state disabled \ -background gold \ + -foreground black \ -activebackground gold bind $w_back <Button-1> " if {\[$w_back cget -state\] eq {normal}} { @@ -98,16 +93,19 @@ constructor new {i_commit i_path} { label $w.header.commit \ -textvariable @commit \ -background gold \ + -foreground black \ -anchor w \ -justify left label $w.header.path_l \ -text [mc "File:"] \ -background gold \ + -foreground black \ -anchor w \ -justify left set w_path $w.header.path label $w_path \ -background gold \ + -foreground black \ -anchor w \ -justify left pack $w.header.commit_l -side left @@ -135,7 +133,9 @@ constructor new {i_commit i_path} { -takefocus 0 \ -highlightthickness 0 \ -padx 0 -pady 0 \ - -background white -borderwidth 0 \ + -background white \ + -foreground black \ + -borderwidth 0 \ -state disabled \ -wrap none \ -height 40 \ @@ -148,7 +148,9 @@ constructor new {i_commit i_path} { -takefocus 0 \ -highlightthickness 0 \ -padx 0 -pady 0 \ - -background white -borderwidth 0 \ + -background white \ + -foreground black \ + -borderwidth 0 \ -state disabled \ -wrap none \ -height 40 \ @@ -166,7 +168,9 @@ constructor new {i_commit i_path} { -takefocus 0 \ -highlightthickness 0 \ -padx 0 -pady 0 \ - -background white -borderwidth 0 \ + -background white \ + -foreground black \ + -borderwidth 0 \ -state disabled \ -wrap none \ -height 40 \ @@ -184,7 +188,9 @@ constructor new {i_commit i_path} { -takefocus 0 \ -highlightthickness 0 \ -padx 0 -pady 0 \ - -background white -borderwidth 0 \ + -background white \ + -foreground black \ + -borderwidth 0 \ -state disabled \ -wrap none \ -height 40 \ @@ -213,7 +219,9 @@ constructor new {i_commit i_path} { set w_cviewer $w.file_pane.cm.t text $w_cviewer \ - -background white -borderwidth 0 \ + -background white \ + -foreground black \ + -borderwidth 0 \ -state disabled \ -wrap none \ -height 10 \ @@ -248,6 +256,9 @@ constructor new {i_commit i_path} { $w.ctxm add command \ -label [mc "Copy Commit"] \ -command [cb _copycommit] + $w.ctxm add command \ + -label [mc "Do Full Copy Detection"] \ + -command [cb _fullcopyblame] foreach i $w_columns { for {set g 0} {$g < [llength $group_colors]} {incr g} { @@ -318,19 +329,27 @@ constructor new {i_commit i_path} { bind $w.file_pane <Configure> \ "if {{$w.file_pane} eq {%W}} {[cb _resize %h]}" + wm protocol $top WM_DELETE_WINDOW "destroy $top" + bind $top <Destroy> [cb _kill] + _load $this {} } +method _kill {} { + if {$current_fd ne {}} { + kill_file_process $current_fd + catch {close $current_fd} + set current_fd {} + } +} + method _load {jump} { variable group_colors _hide_tooltip $this if {$total_lines != 0 || $current_fd ne {}} { - if {$current_fd ne {}} { - catch {close $current_fd} - set current_fd {} - } + _kill $this foreach i $w_columns { $i conf -state normal @@ -496,7 +515,6 @@ method _exec_blame {cur_w cur_d options cur_s} { method _read_blame {fd cur_w cur_d} { upvar #0 $cur_d line_data variable group_colors - variable original_options if {$fd ne $current_fd} { catch {close $fd} @@ -669,6 +687,18 @@ method _read_blame {fd cur_w cur_d} { if {[eof $fd]} { close $fd if {$cur_w eq $w_asim} { + # Switches for original location detection + set threshold [get_config gui.copyblamethreshold] + set original_options [list "-C$threshold"] + + if {![is_config_true gui.fastcopyblame]} { + # thorough copy search; insert before the threshold + set original_options [linsert $original_options 0 -C] + } + if {[git-version >= 1.5.3]} { + lappend original_options -w ; # ignore indentation changes + } + _exec_blame $this $w_amov @amov_data \ $original_options \ [mc "Loading original location annotations..."] @@ -681,6 +711,72 @@ method _read_blame {fd cur_w cur_d} { } } ifdeleted { catch {close $fd} } +method _find_commit_bound {data_list start_idx delta} { + upvar #0 $data_list line_data + set pos $start_idx + set limit [expr {[llength $line_data] - 1}] + set base_commit [lindex $line_data $pos 0] + + while {$pos > 0 && $pos < $limit} { + set new_pos [expr {$pos + $delta}] + if {[lindex $line_data $new_pos 0] ne $base_commit} { + return $pos + } + + set pos $new_pos + } + + return $pos +} + +method _fullcopyblame {} { + if {$current_fd ne {}} { + tk_messageBox \ + -icon error \ + -type ok \ + -title [mc "Busy"] \ + -message [mc "Annotation process is already running."] + + return + } + + # Switches for original location detection + set threshold [get_config gui.copyblamethreshold] + set original_options [list -C -C "-C$threshold"] + + if {[git-version >= 1.5.3]} { + lappend original_options -w ; # ignore indentation changes + } + + # Find the line range + set pos @$::cursorX,$::cursorY + set lno [lindex [split [$::cursorW index $pos] .] 0] + set min_amov_lno [_find_commit_bound $this @amov_data $lno -1] + set max_amov_lno [_find_commit_bound $this @amov_data $lno 1] + set min_asim_lno [_find_commit_bound $this @asim_data $lno -1] + set max_asim_lno [_find_commit_bound $this @asim_data $lno 1] + + if {$min_asim_lno < $min_amov_lno} { + set min_amov_lno $min_asim_lno + } + + if {$max_asim_lno > $max_amov_lno} { + set max_amov_lno $max_asim_lno + } + + lappend original_options -L "$min_amov_lno,$max_amov_lno" + + # Clear lines + for {set i $min_amov_lno} {$i <= $max_amov_lno} {incr i} { + lset amov_data $i [list ] + } + + # Start the back-end process + _exec_blame $this $w_amov @amov_data \ + $original_options \ + [mc "Running thorough copy detection..."] +} + method _click {cur_w pos} { set lno [lindex [split [$cur_w index $pos] .] 0] _showcommit $this $cur_w $lno diff --git a/git-gui/lib/branch_create.tcl b/git-gui/lib/branch_create.tcl index 53dfb4ce6b..3817771b94 100644 --- a/git-gui/lib/branch_create.tcl +++ b/git-gui/lib/branch_create.tcl @@ -183,6 +183,9 @@ method _create {} { if {$spec ne {} && $opt_fetch} { $co enable_fetch $spec } + if {$spec ne {}} { + $co remote_source $spec + } if {[$co run]} { destroy $w diff --git a/git-gui/lib/branch_delete.tcl b/git-gui/lib/branch_delete.tcl index 86c4f73370..ef1930b491 100644 --- a/git-gui/lib/branch_delete.tcl +++ b/git-gui/lib/branch_delete.tcl @@ -127,7 +127,7 @@ method _delete {} { foreach i $to_delete { set b [lindex $i 0] set o [lindex $i 1] - if {[catch {git update-ref -d "refs/heads/$b" $o} err]} { + if {[catch {git branch -D $b} err]} { append failed " - $b: $err\n" } } diff --git a/git-gui/lib/browser.tcl b/git-gui/lib/browser.tcl index 53d5a62816..ab470d1264 100644 --- a/git-gui/lib/browser.tcl +++ b/git-gui/lib/browser.tcl @@ -39,7 +39,8 @@ constructor new {commit {path {}}} { frame $w.list set w_list $w.list.l - text $w_list -background white -borderwidth 0 \ + text $w_list -background white -foreground black \ + -borderwidth 0 \ -cursor $cursor_ptr \ -state disabled \ -wrap none \ diff --git a/git-gui/lib/checkout_op.tcl b/git-gui/lib/checkout_op.tcl index f243966924..caca88831b 100644 --- a/git-gui/lib/checkout_op.tcl +++ b/git-gui/lib/checkout_op.tcl @@ -16,6 +16,7 @@ field merge_base {}; # merge base if we have another ref involved field fetch_spec {}; # refetch tracking branch if used? field checkout 1; # actually checkout the branch? field create 0; # create the branch if it doesn't exist? +field remote_source {}; # same as fetch_spec, to setup tracking field reset_ok 0; # did the user agree to reset? field fetch_ok 0; # did the fetch succeed? @@ -44,6 +45,10 @@ method enable_fetch {spec} { set fetch_spec $spec } +method remote_source {spec} { + set remote_source $spec +} + method enable_checkout {co} { set checkout $co } @@ -145,7 +150,7 @@ method _finish_fetch {ok} { } method _update_ref {} { - global null_sha1 current_branch + global null_sha1 current_branch repo_config set ref $new_ref set new $new_hash @@ -172,6 +177,23 @@ method _update_ref {} { set reflog_msg "branch: Created from $new_expr" set cur $null_sha1 + + if {($repo_config(branch.autosetupmerge) eq {true} + || $repo_config(branch.autosetupmerge) eq {always}) + && $remote_source ne {} + && "refs/heads/$newbranch" eq $ref} { + + set c_remote [lindex $remote_source 1] + set c_merge [lindex $remote_source 2] + if {[catch { + git config branch.$newbranch.remote $c_remote + git config branch.$newbranch.merge $c_merge + } err]} { + _error $this [strcat \ + [mc "Failed to configure simplified git-pull for '%s'." $newbranch] \ + "\n\n$err"] + } + } } elseif {$create && $merge_type eq {none}} { # We were told to create it, but not do a merge. # Bad. Name shouldn't have existed. @@ -280,7 +302,7 @@ The rescan will be automatically started now. } elseif {[is_config_true gui.trustmtime]} { _readtree $this } else { - ui_status {Refreshing file status...} + ui_status [mc "Refreshing file status..."] set fd [git_read update-index \ -q \ --unmerged \ @@ -320,7 +342,7 @@ method _readtree {} { set readtree_d {} $::main_status start \ [mc "Updating working directory to '%s'..." [_name $this]] \ - {files checked out} + [mc "files checked out"] set fd [git_read --stderr read-tree \ -m \ @@ -447,7 +469,7 @@ If you wanted to be on a branch, create one now starting from 'This Detached Che } else { repository_state commit_type HEAD MERGE_HEAD set PARENT $HEAD - ui_status "Checked out '$name'." + ui_status [mc "Checked out '%s'." $name] } delete_this } diff --git a/git-gui/lib/choose_font.tcl b/git-gui/lib/choose_font.tcl index 0c4051b375..56443b042c 100644 --- a/git-gui/lib/choose_font.tcl +++ b/git-gui/lib/choose_font.tcl @@ -55,6 +55,7 @@ constructor pick {path title a_family a_size} { set w_family $w.inner.family.v text $w_family \ -background white \ + -foreground black \ -borderwidth 1 \ -relief sunken \ -cursor $::cursor_ptr \ @@ -92,6 +93,7 @@ constructor pick {path title a_family a_size} { set w_example $w.example.t text $w_example \ -background white \ + -foreground black \ -borderwidth 1 \ -relief sunken \ -height 3 \ diff --git a/git-gui/lib/choose_repository.tcl b/git-gui/lib/choose_repository.tcl index 86faf24cc8..3180786158 100644 --- a/git-gui/lib/choose_repository.tcl +++ b/git-gui/lib/choose_repository.tcl @@ -11,6 +11,7 @@ field w_quit ; # Quit button field o_cons ; # Console object (if active) field w_types ; # List of type buttons in clone field w_recentlist ; # Listbox containing recent repositories +field w_localpath ; # Entry widget bound to local_path field done 0 ; # Finished picking the repository? field local_path {} ; # Where this repository is locally @@ -37,7 +38,7 @@ constructor pick {} { menu $m_repo if {[is_MacOSX]} { - $w.mbar add cascade -label [mc Apple] -menu .mbar.apple + $w.mbar add cascade -label Apple -menu .mbar.apple menu $w.mbar.apple $w.mbar.apple add command \ -label [mc "About %s" [appname]] \ @@ -385,10 +386,9 @@ method _do_new {} { button $w_body.where.b \ -text [mc "Browse"] \ -command [cb _new_local_path] + set w_localpath $w_body.where.t - pack $w_body.where.b -side right - pack $w_body.where.l -side left - pack $w_body.where.t -fill x + grid $w_body.where.l $w_body.where.t $w_body.where.b -sticky ew pack $w_body.where -fill x trace add variable @local_path write [cb _write_local_path] @@ -416,6 +416,7 @@ method _new_local_path {} { return } set local_path $p + $w_localpath icursor end } method _do_new2 {} { @@ -481,6 +482,7 @@ method _do_clone {} { -text [mc "Browse"] \ -command [cb _new_local_path] grid $args.where_l $args.where_t $args.where_b -sticky ew + set w_localpath $args.where_t label $args.type_l -text [mc "Clone Type:"] frame $args.type_f @@ -983,9 +985,7 @@ method _do_open {} { -text [mc "Browse"] \ -command [cb _open_local_path] - pack $w_body.where.b -side right - pack $w_body.where.l -side left - pack $w_body.where.t -fill x + grid $w_body.where.l $w_body.where.t $w_body.where.b -sticky ew pack $w_body.where -fill x trace add variable @local_path write [cb _write_local_path] diff --git a/git-gui/lib/choose_rev.tcl b/git-gui/lib/choose_rev.tcl index a063c5bc49..c8821c1463 100644 --- a/git-gui/lib/choose_rev.tcl +++ b/git-gui/lib/choose_rev.tcl @@ -451,7 +451,8 @@ method _sb_set {sb orient first last} { focus $old_focus } } - $sb set $first $last + + catch {$sb set $first $last} } method _show_tooltip {pos} { diff --git a/git-gui/lib/commit.tcl b/git-gui/lib/commit.tcl index 1c0586c409..40a7103557 100644 --- a/git-gui/lib/commit.tcl +++ b/git-gui/lib/commit.tcl @@ -192,45 +192,52 @@ A good commit message has the following format: return } - # -- Run the pre-commit hook. + # -- Build the message file. # - set pchook [gitdir hooks pre-commit] + set msg_p [gitdir GITGUI_EDITMSG] + set msg_wt [open $msg_p w] + fconfigure $msg_wt -translation lf + if {[catch {set enc $repo_config(i18n.commitencoding)}]} { + set enc utf-8 + } + set use_enc [tcl_encoding $enc] + if {$use_enc ne {}} { + fconfigure $msg_wt -encoding $use_enc + } else { + puts stderr [mc "warning: Tcl does not support encoding '%s'." $enc] + fconfigure $msg_wt -encoding utf-8 + } + puts $msg_wt $msg + close $msg_wt - # On Cygwin [file executable] might lie so we need to ask - # the shell if the hook is executable. Yes that's annoying. + # -- Run the pre-commit hook. # - if {[is_Cygwin] && [file isfile $pchook]} { - set pchook [list sh -c [concat \ - "if test -x \"$pchook\";" \ - "then exec \"$pchook\" 2>&1;" \ - "fi"]] - } elseif {[file executable $pchook]} { - set pchook [list $pchook |& cat] - } else { - commit_writetree $curHEAD $msg + set fd_ph [githook_read pre-commit] + if {$fd_ph eq {}} { + commit_commitmsg $curHEAD $msg_p return } - ui_status {Calling pre-commit hook...} + ui_status [mc "Calling pre-commit hook..."] set pch_error {} - set fd_ph [open "| $pchook" r] fconfigure $fd_ph -blocking 0 -translation binary -eofchar {} fileevent $fd_ph readable \ - [list commit_prehook_wait $fd_ph $curHEAD $msg] + [list commit_prehook_wait $fd_ph $curHEAD $msg_p] } -proc commit_prehook_wait {fd_ph curHEAD msg} { +proc commit_prehook_wait {fd_ph curHEAD msg_p} { global pch_error append pch_error [read $fd_ph] fconfigure $fd_ph -blocking 1 if {[eof $fd_ph]} { if {[catch {close $fd_ph}]} { - ui_status {Commit declined by pre-commit hook.} + catch {file delete $msg_p} + ui_status [mc "Commit declined by pre-commit hook."] hook_failed_popup pre-commit $pch_error unlock_index } else { - commit_writetree $curHEAD $msg + commit_commitmsg $curHEAD $msg_p } set pch_error {} return @@ -238,14 +245,52 @@ proc commit_prehook_wait {fd_ph curHEAD msg} { fconfigure $fd_ph -blocking 0 } -proc commit_writetree {curHEAD msg} { - ui_status {Committing changes...} +proc commit_commitmsg {curHEAD msg_p} { + global pch_error + + # -- Run the commit-msg hook. + # + set fd_ph [githook_read commit-msg $msg_p] + if {$fd_ph eq {}} { + commit_writetree $curHEAD $msg_p + return + } + + ui_status [mc "Calling commit-msg hook..."] + set pch_error {} + fconfigure $fd_ph -blocking 0 -translation binary -eofchar {} + fileevent $fd_ph readable \ + [list commit_commitmsg_wait $fd_ph $curHEAD $msg_p] +} + +proc commit_commitmsg_wait {fd_ph curHEAD msg_p} { + global pch_error + + append pch_error [read $fd_ph] + fconfigure $fd_ph -blocking 1 + if {[eof $fd_ph]} { + if {[catch {close $fd_ph}]} { + catch {file delete $msg_p} + ui_status [mc "Commit declined by commit-msg hook."] + hook_failed_popup commit-msg $pch_error + unlock_index + } else { + commit_writetree $curHEAD $msg_p + } + set pch_error {} + return + } + fconfigure $fd_ph -blocking 0 +} + +proc commit_writetree {curHEAD msg_p} { + ui_status [mc "Committing changes..."] set fd_wt [git_read write-tree] fileevent $fd_wt readable \ - [list commit_committree $fd_wt $curHEAD $msg] + [list commit_committree $fd_wt $curHEAD $msg_p] } -proc commit_committree {fd_wt curHEAD msg} { +proc commit_committree {fd_wt curHEAD msg_p} { global HEAD PARENT MERGE_HEAD commit_type global current_branch global ui_comm selected_commit_type @@ -254,8 +299,9 @@ proc commit_committree {fd_wt curHEAD msg} { gets $fd_wt tree_id if {[catch {close $fd_wt} err]} { + catch {file delete $msg_p} error_popup [strcat [mc "write-tree failed:"] "\n\n$err"] - ui_status {Commit failed.} + ui_status [mc "Commit failed."] unlock_index return } @@ -276,6 +322,7 @@ proc commit_committree {fd_wt curHEAD msg} { } if {$tree_id eq $old_tree} { + catch {file delete $msg_p} info_popup [mc "No changes to commit. No files were modified by this commit and it was not a merge commit. @@ -288,24 +335,6 @@ A rescan will be automatically started now. } } - # -- Build the message. - # - set msg_p [gitdir COMMIT_EDITMSG] - set msg_wt [open $msg_p w] - fconfigure $msg_wt -translation lf - if {[catch {set enc $repo_config(i18n.commitencoding)}]} { - set enc utf-8 - } - set use_enc [tcl_encoding $enc] - if {$use_enc ne {}} { - fconfigure $msg_wt -encoding $use_enc - } else { - puts stderr [mc "warning: Tcl does not support encoding '%s'." $enc] - fconfigure $msg_wt -encoding utf-8 - } - puts $msg_wt $msg - close $msg_wt - # -- Create the commit. # set cmd [list commit-tree $tree_id] @@ -314,8 +343,9 @@ A rescan will be automatically started now. } lappend cmd <$msg_p if {[catch {set cmt_id [eval git $cmd]} err]} { + catch {file delete $msg_p} error_popup [strcat [mc "commit-tree failed:"] "\n\n$err"] - ui_status {Commit failed.} + ui_status [mc "Commit failed."] unlock_index return } @@ -326,18 +356,16 @@ A rescan will be automatically started now. if {$commit_type ne {normal}} { append reflogm " ($commit_type)" } - set i [string first "\n" $msg] - if {$i >= 0} { - set subject [string range $msg 0 [expr {$i - 1}]] - } else { - set subject $msg - } + set msg_fd [open $msg_p r] + gets $msg_fd subject + close $msg_fd append reflogm {: } $subject if {[catch { git update-ref -m $reflogm HEAD $cmt_id $curHEAD } err]} { + catch {file delete $msg_p} error_popup [strcat [mc "update-ref failed:"] "\n\n$err"] - ui_status {Commit failed.} + ui_status [mc "Commit failed."] unlock_index return } @@ -363,17 +391,13 @@ A rescan will be automatically started now. # -- Run the post-commit hook. # - set pchook [gitdir hooks post-commit] - if {[is_Cygwin] && [file isfile $pchook]} { - set pchook [list sh -c [concat \ - "if test -x \"$pchook\";" \ - "then exec \"$pchook\";" \ - "fi"]] - } elseif {![file executable $pchook]} { - set pchook {} - } - if {$pchook ne {}} { - catch {exec $pchook &} + set fd_ph [githook_read post-commit] + if {$fd_ph ne {}} { + upvar #0 pch_error$cmt_id pc_err + set pc_err {} + fconfigure $fd_ph -blocking 0 -translation binary -eofchar {} + fileevent $fd_ph readable \ + [list commit_postcommit_wait $fd_ph $cmt_id] } $ui_comm delete 0.0 end @@ -429,3 +453,18 @@ A rescan will be automatically started now. reshow_diff ui_status [mc "Created commit %s: %s" [string range $cmt_id 0 7] $subject] } + +proc commit_postcommit_wait {fd_ph cmt_id} { + upvar #0 pch_error$cmt_id pch_error + + append pch_error [read $fd_ph] + fconfigure $fd_ph -blocking 1 + if {[eof $fd_ph]} { + if {[catch {close $fd_ph}]} { + hook_failed_popup post-commit $pch_error 0 + } + unset pch_error + return + } + fconfigure $fd_ph -blocking 0 +} diff --git a/git-gui/lib/console.tcl b/git-gui/lib/console.tcl index 5597188d80..c112464ec3 100644 --- a/git-gui/lib/console.tcl +++ b/git-gui/lib/console.tcl @@ -46,7 +46,9 @@ method _init {} { -justify left \ -font font_uibold text $w_t \ - -background white -borderwidth 1 \ + -background white \ + -foreground black \ + -borderwidth 1 \ -relief sunken \ -width 80 -height 10 \ -wrap none \ @@ -180,7 +182,8 @@ method done {ok} { if {$ok} { if {[winfo exists $w.m.s]} { bind $w.m.s <Destroy> [list delete_this $this] - $w.m.s conf -background green -text [mc "Success"] + $w.m.s conf -background green -foreground black \ + -text [mc "Success"] if {$is_toplevel} { $w.ok conf -state normal focus $w.ok @@ -193,7 +196,8 @@ method done {ok} { _init $this } bind $w.m.s <Destroy> [list delete_this $this] - $w.m.s conf -background red -text [mc "Error: Command Failed"] + $w.m.s conf -background red -foreground black \ + -text [mc "Error: Command Failed"] if {$is_toplevel} { $w.ok conf -state normal focus $w.ok diff --git a/git-gui/lib/database.tcl b/git-gui/lib/database.tcl index d66aa3fe33..a18ac8b430 100644 --- a/git-gui/lib/database.tcl +++ b/git-gui/lib/database.tcl @@ -102,8 +102,8 @@ proc hint_gc {} { *]] if {$objects_current >= $object_limit} { - set objects_current [expr {$objects_current * 256}] - set object_limit [expr {$object_limit * 256}] + set objects_current [expr {$objects_current * 250}] + set object_limit [expr {$object_limit * 250}] if {[ask_popup \ [mc "This repository currently has approximately %i loose objects. diff --git a/git-gui/lib/diff.tcl b/git-gui/lib/diff.tcl index d04f6dbde2..52b79e4a1f 100644 --- a/git-gui/lib/diff.tcl +++ b/git-gui/lib/diff.tcl @@ -19,6 +19,7 @@ proc clear_diff {} { proc reshow_diff {} { global file_states file_lists global current_diff_path current_diff_side + global ui_diff set p $current_diff_path if {$p eq {}} { @@ -28,7 +29,8 @@ proc reshow_diff {} { || [lsearch -sorted -exact $file_lists($current_diff_side) $p] == -1} { clear_diff } else { - show_diff $p $current_diff_side + set save_pos [lindex [$ui_diff yview] 0] + show_diff $p $current_diff_side {} $save_pos } } @@ -52,7 +54,7 @@ A rescan will be automatically started to find other files which may have the sa rescan ui_ready 0 } -proc show_diff {path w {lno {}}} { +proc show_diff {path w {lno {}} {scroll_pos {}}} { global file_states file_lists global is_3way_diff diff_active repo_config global ui_diff ui_index ui_workdir @@ -151,6 +153,10 @@ proc show_diff {path w {lno {}}} { $ui_diff conf -state disabled set diff_active 0 unlock_index + if {$scroll_pos ne {}} { + update + $ui_diff yview moveto $scroll_pos + } ui_ready return } @@ -190,10 +196,10 @@ proc show_diff {path w {lno {}}} { -blocking 0 \ -encoding binary \ -translation binary - fileevent $fd readable [list read_diff $fd] + fileevent $fd readable [list read_diff $fd $scroll_pos] } -proc read_diff {fd} { +proc read_diff {fd scroll_pos} { global ui_diff diff_active global is_3way_diff current_diff_header @@ -282,6 +288,10 @@ proc read_diff {fd} { close $fd set diff_active 0 unlock_index + if {$scroll_pos ne {}} { + update + $ui_diff yview moveto $scroll_pos + } ui_ready if {[$ui_diff index end] eq {2.0}} { @@ -362,3 +372,148 @@ proc apply_hunk {x y} { set current_diff_path $current_diff_path } } + +proc apply_line {x y} { + global current_diff_path current_diff_header current_diff_side + global ui_diff ui_index file_states + + if {$current_diff_path eq {} || $current_diff_header eq {}} return + if {![lock_index apply_hunk]} return + + set apply_cmd {apply --cached --whitespace=nowarn} + set mi [lindex $file_states($current_diff_path) 0] + if {$current_diff_side eq $ui_index} { + set failed_msg [mc "Failed to unstage selected line."] + set to_context {+} + lappend apply_cmd --reverse + if {[string index $mi 0] ne {M}} { + unlock_index + return + } + } else { + set failed_msg [mc "Failed to stage selected line."] + set to_context {-} + if {[string index $mi 1] ne {M}} { + unlock_index + return + } + } + + set the_l [$ui_diff index @$x,$y] + + # operate only on change lines + set c1 [$ui_diff get "$the_l linestart"] + if {$c1 ne {+} && $c1 ne {-}} { + unlock_index + return + } + set sign $c1 + + set i_l [$ui_diff search -backwards -regexp ^@@ $the_l 0.0] + if {$i_l eq {}} { + unlock_index + return + } + # $i_l is now at the beginning of a line + + # pick start line number from hunk header + set hh [$ui_diff get $i_l "$i_l + 1 lines"] + set hh [lindex [split $hh ,] 0] + set hln [lindex [split $hh -] 1] + + # There is a special situation to take care of. Consider this hunk: + # + # @@ -10,4 +10,4 @@ + # context before + # -old 1 + # -old 2 + # +new 1 + # +new 2 + # context after + # + # We used to keep the context lines in the order they appear in the + # hunk. But then it is not possible to correctly stage only + # "-old 1" and "+new 1" - it would result in this staged text: + # + # context before + # old 2 + # new 1 + # context after + # + # (By symmetry it is not possible to *un*stage "old 2" and "new 2".) + # + # We resolve the problem by introducing an asymmetry, namely, when + # a "+" line is *staged*, it is moved in front of the context lines + # that are generated from the "-" lines that are immediately before + # the "+" block. That is, we construct this patch: + # + # @@ -10,4 +10,5 @@ + # context before + # +new 1 + # old 1 + # old 2 + # context after + # + # But we do *not* treat "-" lines that are *un*staged in a special + # way. + # + # With this asymmetry it is possible to stage the change + # "old 1" -> "new 1" directly, and to stage the change + # "old 2" -> "new 2" by first staging the entire hunk and + # then unstaging the change "old 1" -> "new 1". + + # This is non-empty if and only if we are _staging_ changes; + # then it accumulates the consecutive "-" lines (after converting + # them to context lines) in order to be moved after the "+" change + # line. + set pre_context {} + + set n 0 + set i_l [$ui_diff index "$i_l + 1 lines"] + set patch {} + while {[$ui_diff compare $i_l < "end - 1 chars"] && + [$ui_diff get $i_l "$i_l + 2 chars"] ne {@@}} { + set next_l [$ui_diff index "$i_l + 1 lines"] + set c1 [$ui_diff get $i_l] + if {[$ui_diff compare $i_l <= $the_l] && + [$ui_diff compare $the_l < $next_l]} { + # the line to stage/unstage + set ln [$ui_diff get $i_l $next_l] + if {$c1 eq {-}} { + set n [expr $n+1] + set patch "$patch$pre_context$ln" + } else { + set patch "$patch$ln$pre_context" + } + set pre_context {} + } elseif {$c1 ne {-} && $c1 ne {+}} { + # context line + set ln [$ui_diff get $i_l $next_l] + set patch "$patch$pre_context$ln" + set n [expr $n+1] + set pre_context {} + } elseif {$c1 eq $to_context} { + # turn change line into context line + set ln [$ui_diff get "$i_l + 1 chars" $next_l] + if {$c1 eq {-}} { + set pre_context "$pre_context $ln" + } else { + set patch "$patch $ln" + } + set n [expr $n+1] + } + set i_l $next_l + } + set patch "@@ -$hln,$n +$hln,[eval expr $n $sign 1] @@\n$patch" + + if {[catch { + set p [eval git_write $apply_cmd] + fconfigure $p -translation binary -encoding binary + puts -nonewline $p $current_diff_header + puts -nonewline $p $patch + close $p} err]} { + error_popup [append $failed_msg "\n\n$err"] + } + + unlock_index +} diff --git a/git-gui/lib/error.tcl b/git-gui/lib/error.tcl index 13565b7ab0..75650157e5 100644 --- a/git-gui/lib/error.tcl +++ b/git-gui/lib/error.tcl @@ -1,6 +1,14 @@ # git-gui branch (create/delete) support # Copyright (C) 2006, 2007 Shawn Pearce +proc _error_parent {} { + set p [grab current .] + if {$p eq {}} { + return . + } + return $p +} + proc error_popup {msg} { set title [appname] if {[reponame] ne {}} { @@ -11,8 +19,8 @@ proc error_popup {msg} { -type ok \ -title [append "$title: " [mc "error"]] \ -message $msg] - if {[winfo ismapped .]} { - lappend cmd -parent . + if {[winfo ismapped [_error_parent]]} { + lappend cmd -parent [_error_parent] } eval $cmd } @@ -27,19 +35,19 @@ proc warn_popup {msg} { -type ok \ -title [append "$title: " [mc "warning"]] \ -message $msg] - if {[winfo ismapped .]} { - lappend cmd -parent . + if {[winfo ismapped [_error_parent]]} { + lappend cmd -parent [_error_parent] } eval $cmd } -proc info_popup {msg {parent .}} { +proc info_popup {msg} { set title [appname] if {[reponame] ne {}} { append title " ([reponame])" } tk_messageBox \ - -parent $parent \ + -parent [_error_parent] \ -icon info \ -type ok \ -title $title \ @@ -56,13 +64,13 @@ proc ask_popup {msg} { -type yesno \ -title $title \ -message $msg] - if {[winfo ismapped .]} { - lappend cmd -parent . + if {[winfo ismapped [_error_parent]]} { + lappend cmd -parent [_error_parent] } eval $cmd } -proc hook_failed_popup {hook msg} { +proc hook_failed_popup {hook msg {is_fatal 1}} { set w .hookfail toplevel $w @@ -72,19 +80,23 @@ proc hook_failed_popup {hook msg} { -justify left \ -font font_uibold text $w.m.t \ - -background white -borderwidth 1 \ + -background white \ + -foreground black \ + -borderwidth 1 \ -relief sunken \ -width 80 -height 10 \ -font font_diff \ -yscrollcommand [list $w.m.sby set] - label $w.m.l2 \ - -text [mc "You must correct the above errors before committing."] \ - -anchor w \ - -justify left \ - -font font_uibold scrollbar $w.m.sby -command [list $w.m.t yview] pack $w.m.l1 -side top -fill x - pack $w.m.l2 -side bottom -fill x + if {$is_fatal} { + label $w.m.l2 \ + -text [mc "You must correct the above errors before committing."] \ + -anchor w \ + -justify left \ + -font font_uibold + pack $w.m.l2 -side bottom -fill x + } pack $w.m.sby -side right -fill y pack $w.m.t -side left -fill both -expand 1 pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10 @@ -99,6 +111,6 @@ proc hook_failed_popup {hook msg} { bind $w <Visibility> "grab $w; focus $w" bind $w <Key-Return> "destroy $w" - wm title $w [append "[appname] ([reponame]): " [mc "error"]] + wm title $w [strcat "[appname] ([reponame]): " [mc "error"]] tkwait window $w } diff --git a/git-gui/lib/index.tcl b/git-gui/lib/index.tcl index 30a244cc17..3c1fce7475 100644 --- a/git-gui/lib/index.tcl +++ b/git-gui/lib/index.tcl @@ -310,7 +310,7 @@ proc add_helper {txt paths} { update_index \ $txt \ $pathList \ - [concat $after {ui_status {Ready to commit.}}] + [concat $after {ui_status [mc "Ready to commit."]}] } } diff --git a/git-gui/lib/merge.tcl b/git-gui/lib/merge.tcl index 63e14279c1..5c01875b05 100644 --- a/git-gui/lib/merge.tcl +++ b/git-gui/lib/merge.tcl @@ -116,8 +116,7 @@ method _start {} { lappend cmd HEAD lappend cmd $name - set msg [mc "Merging %s and %s" $current_branch $stitle] - ui_status "$msg..." + ui_status [mc "Merging %s and %s..." $current_branch $stitle] set cons [console::new [mc "Merge"] "merge $stitle"] console::exec $cons $cmd [cb _finish $cons] @@ -236,7 +235,7 @@ Continue with resetting the current changes?"] set fd [git_read --stderr read-tree --reset -u -v HEAD] fconfigure $fd -blocking 0 -translation binary fileevent $fd readable [namespace code [list _reset_wait $fd]] - $::main_status start [mc "Aborting"] {files reset} + $::main_status start [mc "Aborting"] [mc "files reset"] } else { unlock_index } @@ -258,6 +257,7 @@ proc _reset_wait {fd} { catch {file delete [gitdir MERGE_HEAD]} catch {file delete [gitdir rr-cache MERGE_RR]} + catch {file delete [gitdir MERGE_RR]} catch {file delete [gitdir SQUASH_MSG]} catch {file delete [gitdir MERGE_MSG]} catch {file delete [gitdir GITGUI_MSG]} diff --git a/git-gui/lib/option.tcl b/git-gui/lib/option.tcl index f812e5e89a..ffb3f00ff0 100644 --- a/git-gui/lib/option.tcl +++ b/git-gui/lib/option.tcl @@ -5,6 +5,7 @@ proc save_config {} { global default_config font_descs global repo_config global_config global repo_config_new global_config_new + global ui_comm_spell foreach option $font_descs { set name [lindex $option 0] @@ -52,11 +53,23 @@ proc save_config {} { set repo_config($name) $value } } + + if {[info exists repo_config(gui.spellingdictionary)]} { + set value $repo_config(gui.spellingdictionary) + if {$value eq {none}} { + if {[info exists ui_comm_spell]} { + $ui_comm_spell stop + } + } elseif {[info exists ui_comm_spell]} { + $ui_comm_spell lang $value + } + } } proc do_options {} { global repo_config global_config font_descs global repo_config_new global_config_new + global ui_comm_spell array unset repo_config_new array unset global_config_new @@ -110,7 +123,10 @@ proc do_options {} { {b gui.trustmtime {mc "Trust File Modification Timestamps"}} {b gui.pruneduringfetch {mc "Prune Tracking Branches During Fetch"}} {b gui.matchtrackingbranch {mc "Match Tracking Branches"}} + {b gui.fastcopyblame {mc "Blame Copy Only On Changed Files"}} + {i-20..200 gui.copyblamethreshold {mc "Minimum Letters To Blame Copy On"}} {i-0..99 gui.diffcontext {mc "Number of Diff Context Lines"}} + {i-0..99 gui.commitmsgwidth {mc "Commit Message Text Width"}} {t gui.newbranchtemplate {mc "New Branch Name Template"}} } { set type [lindex $option 0] @@ -159,6 +175,32 @@ proc do_options {} { } } + set all_dicts [linsert \ + [spellcheck::available_langs] \ + 0 \ + none] + incr optid + foreach f {repo global} { + if {![info exists ${f}_config_new(gui.spellingdictionary)]} { + if {[info exists ui_comm_spell]} { + set value [$ui_comm_spell lang] + } else { + set value none + } + set ${f}_config_new(gui.spellingdictionary) $value + } + + frame $w.$f.$optid + label $w.$f.$optid.l -text [mc "Spelling Dictionary:"] + eval tk_optionMenu $w.$f.$optid.v \ + ${f}_config_new(gui.spellingdictionary) \ + $all_dicts + pack $w.$f.$optid.l -side left -anchor w -fill x + pack $w.$f.$optid.v -side right -anchor e -padx 5 + pack $w.$f.$optid -side top -anchor w -fill x + } + unset all_dicts + set all_fonts [lsort [font families]] foreach option $font_descs { set name [lindex $option 0] diff --git a/git-gui/lib/spellcheck.tcl b/git-gui/lib/spellcheck.tcl new file mode 100644 index 0000000000..78f344f08f --- /dev/null +++ b/git-gui/lib/spellcheck.tcl @@ -0,0 +1,414 @@ +# git-gui spellchecking support through ispell/aspell +# Copyright (C) 2008 Shawn Pearce + +class spellcheck { + +field s_fd {} ; # pipe to ispell/aspell +field s_version {} ; # ispell/aspell version string +field s_lang {} ; # current language code +field s_prog aspell; # are we actually old ispell? +field s_failed 0 ; # is $s_prog bogus and not working? + +field w_text ; # text widget we are spelling +field w_menu ; # context menu for the widget +field s_menuidx 0 ; # last index of insertion into $w_menu + +field s_i {} ; # timer registration for _run callbacks +field s_clear 0 ; # did we erase mispelled tags yet? +field s_seen [list] ; # lines last seen from $w_text in _run +field s_checked [list] ; # lines already checked +field s_pending [list] ; # [$line $data] sent to ispell/aspell +field s_suggest ; # array, list of suggestions, keyed by misspelling + +constructor init {pipe_fd ui_text ui_menu} { + set w_text $ui_text + set w_menu $ui_menu + array unset s_suggest + + bind_button3 $w_text [cb _popup_suggest %X %Y @%x,%y] + _connect $this $pipe_fd + return $this +} + +method _connect {pipe_fd} { + fconfigure $pipe_fd \ + -encoding utf-8 \ + -eofchar {} \ + -translation lf + + if {[gets $pipe_fd s_version] <= 0} { + if {[catch {close $pipe_fd} err]} { + + # Eh? Is this actually ispell choking on aspell options? + # + if {$s_prog eq {aspell} + && [regexp -nocase {^Usage: } $err] + && ![catch { + set pipe_fd [open [list | $s_prog -v] r] + gets $pipe_fd s_version + close $pipe_fd + }] + && $s_version ne {}} { + if {{@(#) } eq [string range $s_version 0 4]} { + set s_version [string range $s_version 5 end] + } + set s_failed 1 + error_popup [strcat \ + [mc "Unsupported spell checker"] \ + ":\n\n$s_version"] + set s_version {} + return + } + + regsub -nocase {^Error: } $err {} err + if {$s_fd eq {}} { + error_popup [strcat [mc "Spell checking is unavailable"] ":\n\n$err"] + } else { + error_popup [strcat \ + [mc "Invalid spell checking configuration"] \ + ":\n\n$err\n\n" \ + [mc "Reverting dictionary to %s." $s_lang]] + } + } else { + error_popup [mc "Spell checker silently failed on startup"] + } + return + } + + if {{@(#) } ne [string range $s_version 0 4]} { + catch {close $pipe_fd} + error_popup [strcat [mc "Unrecognized spell checker"] ":\n\n$s_version"] + return + } + set s_version [string range $s_version 5 end] + regexp \ + {International Ispell Version .* \(but really (Aspell .*?)\)$} \ + $s_version _junk s_version + regexp {^Aspell (\d)+\.(\d+)} $s_version _junk major minor + + puts $pipe_fd ! ; # enable terse mode + + # fetch the language + if {$major > 0 || ($major == 0 && $minor >= 60)} { + puts $pipe_fd {$$cr master} + flush $pipe_fd + gets $pipe_fd s_lang + regexp {[/\\]([^/\\]+)\.[^\.]+$} $s_lang _ s_lang + } else { + set s_lang {} + } + + if {$::default_config(gui.spellingdictionary) eq {} + && [get_config gui.spellingdictionary] eq {}} { + set ::default_config(gui.spellingdictionary) $s_lang + } + + if {$s_fd ne {}} { + catch {close $s_fd} + } + set s_fd $pipe_fd + + fconfigure $s_fd -blocking 0 + fileevent $s_fd readable [cb _read] + + $w_text tag conf misspelled \ + -foreground red \ + -underline 1 + + array unset s_suggest + set s_seen [list] + set s_checked [list] + set s_pending [list] + _run $this +} + +method lang {{n {}}} { + if {$n ne {} && $s_lang ne $n && !$s_failed} { + set spell_cmd [list |] + lappend spell_cmd aspell + lappend spell_cmd --master=$n + lappend spell_cmd --mode=none + lappend spell_cmd --encoding=UTF-8 + lappend spell_cmd pipe + _connect $this [open $spell_cmd r+] + } + return $s_lang +} + +method version {} { + if {$s_version ne {}} { + return "$s_version, $s_lang" + } + return {} +} + +method stop {} { + while {$s_menuidx > 0} { + $w_menu delete 0 + incr s_menuidx -1 + } + $w_text tag delete misspelled + + catch {close $s_fd} + catch {after cancel $s_i} + set s_fd {} + set s_i {} + set s_lang {} +} + +method _popup_suggest {X Y pos} { + while {$s_menuidx > 0} { + $w_menu delete 0 + incr s_menuidx -1 + } + + set b_loc [$w_text index "$pos wordstart"] + set e_loc [_wordend $this $b_loc] + set orig [$w_text get $b_loc $e_loc] + set tags [$w_text tag names $b_loc] + + if {[lsearch -exact $tags misspelled] >= 0} { + if {[info exists s_suggest($orig)]} { + set cnt 0 + foreach s $s_suggest($orig) { + if {$cnt < 5} { + $w_menu insert $s_menuidx command \ + -label $s \ + -command [cb _replace $b_loc $e_loc $s] + incr s_menuidx + incr cnt + } else { + break + } + } + } else { + $w_menu insert $s_menuidx command \ + -label [mc "No Suggestions"] \ + -state disabled + incr s_menuidx + } + $w_menu insert $s_menuidx separator + incr s_menuidx + } + + $w_text mark set saved-insert insert + tk_popup $w_menu $X $Y +} + +method _replace {b_loc e_loc word} { + $w_text configure -autoseparators 0 + $w_text edit separator + + $w_text delete $b_loc $e_loc + $w_text insert $b_loc $word + + $w_text edit separator + $w_text configure -autoseparators 1 + $w_text mark set insert saved-insert +} + +method _restart_timer {} { + set s_i [after 300 [cb _run]] +} + +proc _match_length {max_line arr_name} { + upvar $arr_name a + + if {[llength $a] > $max_line} { + set a [lrange $a 0 $max_line] + } + while {[llength $a] <= $max_line} { + lappend a {} + } +} + +method _wordend {pos} { + set pos [$w_text index "$pos wordend"] + set tags [$w_text tag names $pos] + while {[lsearch -exact $tags misspelled] >= 0} { + set pos [$w_text index "$pos +1c"] + set tags [$w_text tag names $pos] + } + return $pos +} + +method _run {} { + set cur_pos [$w_text index {insert -1c}] + set cur_line [lindex [split $cur_pos .] 0] + set max_line [lindex [split [$w_text index end] .] 0] + _match_length $max_line s_seen + _match_length $max_line s_checked + + # Nothing in the message buffer? Nothing to spellcheck. + # + if {$cur_line == 1 + && $max_line == 2 + && [$w_text get 1.0 end] eq "\n"} { + array unset s_suggest + _restart_timer $this + return + } + + set active 0 + for {set n 1} {$n <= $max_line} {incr n} { + set s [$w_text get "$n.0" "$n.end"] + + # Don't spellcheck the current line unless we are at + # a word boundary. The user might be typing on it. + # + if {$n == $cur_line + && ![regexp {^\W$} [$w_text get $cur_pos insert]]} { + + # If the current word is mispelled remove the tag + # but force a spellcheck later. + # + set tags [$w_text tag names $cur_pos] + if {[lsearch -exact $tags misspelled] >= 0} { + $w_text tag remove misspelled \ + "$cur_pos wordstart" \ + [_wordend $this $cur_pos] + lset s_seen $n $s + lset s_checked $n {} + } + + continue + } + + if {[lindex $s_seen $n] eq $s + && [lindex $s_checked $n] ne $s} { + # Don't send empty lines to Aspell it doesn't check them. + # + if {$s eq {}} { + lset s_checked $n $s + continue + } + + # Don't send typical s-b-o lines as the emails are + # almost always misspelled according to Aspell. + # + if {[regexp -nocase {^[a-z-]+-by:.*<.*@.*>$} $s]} { + $w_text tag remove misspelled "$n.0" "$n.end" + lset s_checked $n $s + continue + } + + puts $s_fd ^$s + lappend s_pending [list $n $s] + set active 1 + } else { + # Delay until another idle loop to make sure we don't + # spellcheck lines the user is actively changing. + # + lset s_seen $n $s + } + } + + if {$active} { + set s_clear 1 + flush $s_fd + } else { + _restart_timer $this + } +} + +method _read {} { + while {[gets $s_fd line] >= 0} { + set lineno [lindex $s_pending 0 0] + + if {$s_clear} { + $w_text tag remove misspelled "$lineno.0" "$lineno.end" + set s_clear 0 + } + + if {$line eq {}} { + lset s_checked $lineno [lindex $s_pending 0 1] + set s_pending [lrange $s_pending 1 end] + set s_clear 1 + continue + } + + set sugg [list] + switch -- [string range $line 0 1] { + {& } { + set line [split [string range $line 2 end] :] + set info [split [lindex $line 0] { }] + set orig [lindex $info 0] + set offs [lindex $info 2] + foreach s [split [lindex $line 1] ,] { + lappend sugg [string range $s 1 end] + } + } + {# } { + set info [split [string range $line 2 end] { }] + set orig [lindex $info 0] + set offs [lindex $info 1] + } + default { + puts stderr "<spell> $line" + continue + } + } + + incr offs -1 + set b_loc "$lineno.$offs" + set e_loc [$w_text index "$lineno.$offs wordend"] + set curr [$w_text get $b_loc $e_loc] + + # At least for English curr = "bob", orig = "bob's" + # so Tk didn't include the 's but Aspell did. We + # try to round out the word. + # + while {$curr ne $orig + && [string equal -length [string length $curr] $curr $orig]} { + set n_loc [$w_text index "$e_loc +1c"] + set n_curr [$w_text get $b_loc $n_loc] + if {$n_curr eq $curr} { + break + } + set curr $n_curr + set e_loc $n_loc + } + + if {$curr eq $orig} { + $w_text tag add misspelled $b_loc $e_loc + if {[llength $sugg] > 0} { + set s_suggest($orig) $sugg + } else { + unset -nocomplain s_suggest($orig) + } + } else { + unset -nocomplain s_suggest($orig) + } + } + + fconfigure $s_fd -block 1 + if {[eof $s_fd]} { + if {![catch {close $s_fd} err]} { + set err [mc "Unexpected EOF from spell checker"] + } + catch {after cancel $s_i} + $w_text tag remove misspelled 1.0 end + error_popup [strcat [mc "Spell Checker Failed"] "\n\n" $err] + return + } + fconfigure $s_fd -block 0 + + if {[llength $s_pending] == 0} { + _restart_timer $this + } +} + +proc available_langs {} { + set langs [list] + catch { + set fd [open [list | aspell dump dicts] r] + while {[gets $fd line] >= 0} { + if {$line eq {}} continue + lappend langs $line + } + close $fd + } + return $langs +} + +} |
