summaryrefslogtreecommitdiff
path: root/git-gui/lib
diff options
context:
space:
mode:
authorJunio C Hamano <gitster@pobox.com>2011-07-06 15:37:42 -0700
committerJunio C Hamano <gitster@pobox.com>2011-07-06 15:37:42 -0700
commit4d9e42f8f11c57b32b976a943c8ddaf6214e64b8 (patch)
treef1aee1490288aa30fb62a981696389d9b5e3e992 /git-gui/lib
parentc5bcf1f9f6d3429ab9a09e07e28362e7d189005b (diff)
parentea02eef096d4bfcbb83e76cfab0fcb42dbcad35e (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.tcl6
-rw-r--r--git-gui/lib/blame.tcl130
-rw-r--r--git-gui/lib/branch_create.tcl3
-rw-r--r--git-gui/lib/branch_delete.tcl2
-rw-r--r--git-gui/lib/browser.tcl3
-rw-r--r--git-gui/lib/checkout_op.tcl30
-rw-r--r--git-gui/lib/choose_font.tcl2
-rw-r--r--git-gui/lib/choose_repository.tcl14
-rw-r--r--git-gui/lib/choose_rev.tcl3
-rw-r--r--git-gui/lib/commit.tcl161
-rw-r--r--git-gui/lib/console.tcl10
-rw-r--r--git-gui/lib/database.tcl4
-rw-r--r--git-gui/lib/diff.tcl163
-rw-r--r--git-gui/lib/error.tcl46
-rw-r--r--git-gui/lib/index.tcl2
-rw-r--r--git-gui/lib/merge.tcl6
-rw-r--r--git-gui/lib/option.tcl42
-rw-r--r--git-gui/lib/spellcheck.tcl414
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
+}
+
+}