Skip to content

Commit 95b002e

Browse files
committed
git-gui: Automatically spell check commit messages as the user types
Many user friendly tools like word processors, email editors and web browsers allow users to spell check the message they are writing as they type it, making it easy to identify a common misspelling of a word and correct it on the fly. We now open a bi-directional pipe to Aspell and feed the message text the user is editing off to the program about once every 300 milliseconds. This is frequent enough that the user sees the results almost immediately, but is not so frequent as to cause significant additional load on the system. If the user has modified the message text during the last 300 milliseconds we delay until the next period, ensuring that we avoid flooding the Aspell process with a lot of text while the user is actively typing their message. We wait to send the current message buffer to Aspell until the user is at a word boundary, thus ensuring that we are not likely to ask for misspelled word detection on a word that the user is actively typing, as most words are misspelled when only partially typed, even if the user has thus far typed it correctly. Misspelled words are highlighted in red and are given an underline, causing the word to stand out from the others in the buffer. This is a very common user interface idiom for displaying misspelled words, but differs from one platform to the next in slight variations. For example the Mac OS X system prefers using a dashed red underline, leaving the word in the original text color. Unfortunately the control that Tk gives us over text display is not powerful enough to handle such formatting so we have to work with the least common denominator. The top suggestions for a misspelling are saved in an array and offered to the user when they right-click (or on the Mac ctrl-click) a misspelled word. Selecting an entry from this menu will replace the misspelling with the correction shown. Replacement is integrated with the undo/redo stack so undoing a replacement will restore the misspelled original text. If Aspell could not be started during git-gui launch we silently eat the error and run without spell checking support. This way users who do not have Aspell in their $PATH can continue to use git-gui, although they will not get the advanced spelling functionality. If Aspell started successfully the version line and language are shown in git-gui's about box, below the Tcl/Tk versions. This way the user can verify the Aspell function has been activated. If Aspell crashes while we are running we inform the user with an error dialog and then disable Aspell entirely for the rest of this git-gui session. This prevents us from fork-bombing the system with Aspell instances that always crash when presented with the current message text, should there be a bug in either Aspell or in git-gui's output to it. We escape all input lines with ^, as recommended by the Aspell manual page, as this allows Aspell to properly ignore any input line that is otherwise looking like a command (e.g. ! to enable terse output). By using this escape however we need to correct all word offsets by -1 as Aspell is apparently considering the ^ escape to be part of the line's character count, but our Tk text widget obviously does not. Available dictionaries are offered in the Options dialog, allowing the user to select the language they want to spellcheck commit messages with for the current repository, as well as the global user setting that all repositories inherit. Special thanks to Adam Flott for suggesting connecting git-gui to Aspell for the purpose of spell checking the commit message, and to Wincent Colaiuta for the idea to wait for a word boundary before passing the message over for checking. Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
1 parent 88965d1 commit 95b002e

File tree

4 files changed

+442
-1
lines changed

4 files changed

+442
-1
lines changed

git-gui.sh

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,7 @@ set default_config(gui.pruneduringfetch) false
612612
set default_config(gui.trustmtime) false
613613
set default_config(gui.diffcontext) 5
614614
set default_config(gui.newbranchtemplate) {}
615+
set default_config(gui.spellingdictionary) {}
615616
set default_config(gui.fontui) [font configure font_ui]
616617
set default_config(gui.fontdiff) [font configure font_diff]
617618
set font_descs {
@@ -1683,6 +1684,7 @@ set is_quitting 0
16831684
proc do_quit {} {
16841685
global ui_comm is_quitting repo_config commit_type
16851686
global GITGUI_BCK_exists GITGUI_BCK_i
1687+
global ui_comm_spell
16861688
16871689
if {$is_quitting} return
16881690
set is_quitting 1
@@ -1710,6 +1712,12 @@ proc do_quit {} {
17101712
}
17111713
}
17121714
1715+
# -- Cancel our spellchecker if its running.
1716+
#
1717+
if {[info exists ui_comm_spell]} {
1718+
$ui_comm_spell stop
1719+
}
1720+
17131721
# -- Remove our editor backup, its not needed.
17141722
#
17151723
after cancel $GITGUI_BCK_i
@@ -2454,7 +2462,7 @@ $ctxm add separator
24542462
$ctxm add command \
24552463
-label [mc "Sign Off"] \
24562464
-command do_signoff
2457-
bind_button3 $ui_comm "tk_popup $ctxm %X %Y"
2465+
set ui_comm_ctxm $ctxm
24582466
24592467
# -- Diff Header
24602468
#
@@ -2857,6 +2865,30 @@ if {[winfo exists $ui_comm]} {
28572865
}
28582866
28592867
backup_commit_buffer
2868+
2869+
# -- If the user has aspell available we can drive it
2870+
# in pipe mode to spellcheck the commit message.
2871+
#
2872+
set spell_cmd [list |]
2873+
set spell_dict [get_config gui.spellingdictionary]
2874+
lappend spell_cmd aspell
2875+
if {$spell_dict ne {}} {
2876+
lappend spell_cmd --master=$spell_dict
2877+
}
2878+
lappend spell_cmd --mode=none
2879+
lappend spell_cmd --encoding=utf-8
2880+
lappend spell_cmd pipe
2881+
if {$spell_dict eq {none}
2882+
|| [catch {set spell_fd [open $spell_cmd r+]} spell_err]} {
2883+
bind_button3 $ui_comm [list tk_popup $ui_comm_ctxm %X %Y]
2884+
} else {
2885+
set ui_comm_spell [spellcheck::init \
2886+
$spell_fd \
2887+
$ui_comm \
2888+
$ui_comm_ctxm \
2889+
]
2890+
}
2891+
unset -nocomplain spell_cmd spell_fd spell_err spell_dict
28602892
}
28612893
28622894
lock_index begin-read

lib/about.tcl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
proc do_about {} {
55
global appvers copyright oguilib
66
global tcl_patchLevel tk_patchLevel
7+
global ui_comm_spell
78

89
set w .about_dialog
910
toplevel $w
@@ -40,6 +41,10 @@ proc do_about {} {
4041
append v "Tcl version $tcl_patchLevel"
4142
append v ", Tk version $tk_patchLevel"
4243
}
44+
if {[info exists ui_comm_spell]} {
45+
append v "\n"
46+
append v [$ui_comm_spell version]
47+
}
4348

4449
set d {}
4550
append d "git wrapper: $::_git\n"

lib/option.tcl

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ proc save_config {} {
55
global default_config font_descs
66
global repo_config global_config
77
global repo_config_new global_config_new
8+
global ui_comm_spell
89

910
foreach option $font_descs {
1011
set name [lindex $option 0]
@@ -52,11 +53,23 @@ proc save_config {} {
5253
set repo_config($name) $value
5354
}
5455
}
56+
57+
if {[info exists repo_config(gui.spellingdictionary)]} {
58+
set value $repo_config(gui.spellingdictionary)
59+
if {$value eq {none}} {
60+
if {[info exists ui_comm_spell]} {
61+
$ui_comm_spell stop
62+
}
63+
} elseif {[info exists ui_comm_spell]} {
64+
$ui_comm_spell lang $value
65+
}
66+
}
5567
}
5668

5769
proc do_options {} {
5870
global repo_config global_config font_descs
5971
global repo_config_new global_config_new
72+
global ui_comm_spell
6073

6174
array unset repo_config_new
6275
array unset global_config_new
@@ -159,6 +172,34 @@ proc do_options {} {
159172
}
160173
}
161174

175+
set all_dicts [linsert \
176+
[spellcheck::available_langs] \
177+
0 \
178+
none]
179+
incr optid
180+
foreach f {repo global} {
181+
if {![info exists ${f}_config_new(gui.spellingdictionary)]} {
182+
if {[info exists ui_comm_spell]} {
183+
set value [$ui_comm_spell lang]
184+
} else {
185+
set value none
186+
}
187+
set ${f}_config_new(gui.spellingdictionary) $value
188+
}
189+
190+
frame $w.$f.$optid
191+
label $w.$f.$optid.l -text [mc "Spelling Dictionary:"]
192+
eval tk_optionMenu $w.$f.$optid.v \
193+
${f}_config_new(gui.spellingdictionary) \
194+
$all_dicts
195+
pack $w.$f.$optid.l -side left -anchor w -fill x
196+
pack $w.$f.$optid.v -side left -anchor w \
197+
-fill x -expand 1 \
198+
-padx 5
199+
pack $w.$f.$optid -side top -anchor w -fill x
200+
}
201+
unset all_dicts
202+
162203
set all_fonts [lsort [font families]]
163204
foreach option $font_descs {
164205
set name [lindex $option 0]

0 commit comments

Comments
 (0)