1declare-option -hidden range-specs spell_regions
2declare-option -hidden str spell_last_lang
3
4declare-option -docstring "default language to use when none is passed to the spell-check command" str spell_lang
5
6define-command -params ..1 -docstring %{
7    spell [<language>]: spell check the current buffer
8
9    The first optional argument is the language against which the check will be performed (overrides `spell_lang`)
10    Formats of language supported:
11      - ISO language code, e.g. 'en'
12      - language code above followed by a dash or underscore with an ISO country code, e.g. 'en-US'
13    } spell %{
14    try %{ add-highlighter window/ ranges 'spell_regions' }
15    evaluate-commands %sh{
16        use_lang() {
17            if ! printf %s "$1" | grep -qE '^[a-z]{2,3}([_-][A-Z]{2})?$'; then
18                echo "fail 'Invalid language code (examples of expected format: en, en_US, en-US)'"
19                exit 1
20            else
21                options="-l '$1'"
22                printf 'set-option buffer spell_last_lang %s\n' "$1"
23            fi
24        }
25
26        if [ $# -ge 1 ]; then
27            use_lang "$1"
28        elif [ -n "${kak_opt_spell_lang}" ]; then
29            use_lang "${kak_opt_spell_lang}"
30        fi
31
32        printf 'eval -no-hooks write %s\n' "${kak_response_fifo}" > $kak_command_fifo
33
34        {
35            sed 's/^/^/' | eval "aspell --byte-offsets -a $options" 2>&1 | awk '
36                BEGIN {
37                    line_num = 1
38                    regions = ENVIRON["kak_timestamp"]
39                    server_command = sprintf("kak -p \"%s\"", ENVIRON["kak_session"])
40                }
41
42                {
43                    if (/^@\(#\)/) {
44                        # drop the identification message
45                    }
46
47                    else if (/^\*/) {
48                        # nothing
49                    }
50
51                    else if (/^[+-]/) {
52                        # required to ignore undocumented aspell functionality
53                    }
54
55                    else if (/^$/) {
56                        line_num++
57                    }
58
59                    else if (/^[#&]/) {
60                        word_len = length($2)
61                        word_pos = substr($0, 1, 1) == "&" ? substr($4, 1, length($4) - 1) : $3;
62                        regions = regions " " line_num "." word_pos "+" word_len "|DiagnosticError"
63                    }
64
65                    else {
66                        line = $0
67                        gsub(/"/, "&&", line)
68                        command = "fail \"" line "\""
69                        exit
70                    }
71                }
72
73                END {
74                    if (!length(command))
75                        command = "set-option \"buffer=" ENVIRON["kak_bufname"] "\" spell_regions " regions
76
77                    print command | server_command
78                    close(server_command)
79                }
80            '
81        } <$kak_response_fifo >/dev/null 2>&1 &
82    }
83}
84
85define-command spell-clear %{
86    unset-option buffer spell_regions
87}
88
89define-command spell-next %{ evaluate-commands %sh{
90    anchor_line="${kak_selection_desc%%.*}"
91    anchor_col="${kak_selection_desc%%,*}"
92    anchor_col="${anchor_col##*.}"
93
94    start_first="${kak_opt_spell_regions%%|*}"
95    start_first="${start_first#* }"
96
97    # Make sure properly formatted selection descriptions are in `%opt{spell_regions}`
98    if ! printf %s "${start_first}" | grep -qE '^[0-9]+\.[0-9]+,[0-9]+\.[0-9]+$'; then
99        exit
100    fi
101
102    printf %s "${kak_opt_spell_regions#* }" | awk -v start_first="${start_first}" \
103                                                  -v anchor_line="${anchor_line}" \
104                                                  -v anchor_col="${anchor_col}" '
105        BEGIN {
106            anchor_line = int(anchor_line)
107            anchor_col = int(anchor_col)
108        }
109
110        {
111            for (i = 1; i <= NF; i++) {
112                sel = $i
113                sub(/\|.+$/, "", sel)
114
115                start_line = sel
116                sub(/\..+$/, "", start_line)
117                start_line = int(start_line)
118
119                start_col = sel
120                sub(/,.+$/, "", start_col)
121                sub(/^.+\./, "", start_col)
122                start_col = int(start_col)
123
124                if (start_line < anchor_line \
125                    || (start_line == anchor_line && start_col <= anchor_col))
126                    continue
127
128                target_sel = sel
129                break
130            }
131        }
132
133        END {
134            if (!target_sel)
135                target_sel = start_first
136
137            printf "select %s\n", target_sel
138        }'
139} }
140
141define-command \
142    -docstring "Suggest replacement words for the current selection, against the last language used by the spell-check command" \
143    spell-replace %{
144    prompt \
145        -shell-script-candidates %{
146            options=""
147            if [ -n "$kak_opt_spell_last_lang" ]; then
148                options="-l '$kak_opt_spell_last_lang'"
149            fi
150            printf %s "$kak_selection" |
151                eval "aspell -a $options" |
152                sed -n -e '/^&/ { s/^[^:]*: //; s/, /\n/g; p }'
153        } \
154        "Replace with: " \
155        %{
156            evaluate-commands -save-regs a %{
157                set-register a %val{text}
158                execute-keys c <c-r>a <esc>
159            }
160        }
161}
162
163
164define-command -params 0.. \
165    -docstring "Add the current selection to the dictionary" \
166    spell-add %{ evaluate-commands %sh{
167    options=""
168    if [ -n "$kak_opt_spell_last_lang" ]; then
169        options="-l '$kak_opt_spell_last_lang'"
170    fi
171    if [ $# -eq 0 ]; then
172        # use selections
173        eval set -- "$kak_quoted_selections"
174    fi
175    while [ $# -gt 0 ]; do
176        word="$1"
177        if ! printf '*%s\n#\n' "${word}" | eval "aspell -a $options" >/dev/null; then
178           printf 'fail "Unable to add word: %s"' "$(printf %s "${word}" | sed 's/"/&&/g')"
179           exit 1
180        fi
181        shift
182    done
183}}
184