The Poor Man's Grammar Checker

In my ever growing attempts to make the best Markdown Editor Linux users have ever seen, I wanted to find a solution for grammar checking. write-good offered style checking, and Gtk.Spell handled spelling. Style and Spell Check don’t cover grammar though.

I’m calling the current solution, “The Poor Man’s Grammar Checker.”

I knew about Language Checker, but that would require Java and running a separate service. I thought I can up with an idea for Not-Quite-Grammar, which would do part of speech tagging and highlight unexpected part of speeches.

Turns out, the idea isn’t too original. Link Grammar has been in development and is well tested by AbiWord. There’s no VAPI for it (that I could find). I could write one, but it seemed to have a pretty decent command line. Also, I’m lazy.

So let’s add Grammar Check to kmwriter.

link-parser is the command line tool for Link Grammar. To show if a sentence has errors, we can pipe a sentence to link-parser -batch.

$ echo "he eats cake" | link-parser -batch
Batch mode turned on.
link-grammar: Info: Dictionary found at /usr/share/link-grammar/en/4.0.dict
link-grammar: Info: Dictionary version 5.8.1, locale en_US.UTF-8
link-grammar: Info: Library version link-grammar-5.8.1. Enter "!help" for help.
0 errors.
Bye.

If we send an incorrect sentence:

$ echo "he eat cake" | link-parser -batch
Batch mode turned on.
link-grammar: Info: Dictionary found at /usr/share/link-grammar/en/4.0.dict
link-grammar: Info: Dictionary version 5.8.1, locale en_US.UTF-8
link-grammar: Info: Library version link-grammar-5.8.1. Enter "!help" for help.
+++++ error 1
1 error.
Bye.

Seeing this, we can see if a sentence is error free, we get 0 errors. Otherwise, we get the number of grammar errors.

Which means we can write a function that pipes a sentence to the command, and reads if there are no errors.

Subprocess grammar = new Subprocess.newv (
    command,
    SubprocessFlags.STDOUT_PIPE |
    SubprocessFlags.STDIN_PIPE |
    SubprocessFlags.STDERR_MERGE);

var input_stream = grammar.get_stdin_pipe ();
if (input_stream != null) {
    DataOutputStream flush_buffer = new DataOutputStream (input_stream);
    if (!flush_buffer.put_string (check_sentence)) {
        warning ("Could not set buffer");
    }
    flush_buffer.flush ();
    flush_buffer.close ();
}
var output_stream = grammar.get_stdout_pipe ();
grammar.wait (cancellable);
if (output_stream != null) {
    var proc_input = new DataInputStream (output_stream);
    string line = "";
    while ((line = proc_input.read_line (null)) != null) {
        error_free = error_free || line.down ().contains ("0 errors");
    }
}

This code creates a subprocess to launch link-checker. It passes check_sentence to the standard input, and scans the standard output for “0 errors”.

Next step is to go over all the sentences in our TextBuffer. We can create a Gtk.TextTag to style the grammar errors.

To step through each sentence, we can do:

Gtk.TextIter sentence_start = buffer_start;
Gtk.TextIter sentence_end = buffer_start;
while (sentence_end.forward_sentence_end ()) {
    string sentence = strip_markdown (source_buffer.get_text (sentence_start, sentence_end, false));
    if (!cursor_location.in_range (sentence_start, sentence_end)) {
        if (!grammar_correct_sentence_check (sentence)) {
            source_buffer.apply_tag (grammar_error, sentence_start, sentence_end);
        }
    }
    sentence_start = sentence_end;
}

This portion of the code uses Gtk.TextIter’s magical ability to navigate sentences. We have a separate function to strip markdown characters from the sentence. Once sanitized, the sentence can be checked.

The whole functions looks like:

public void check_grammar () {
    if (!grammar_timer.can_do_action ()) {
        return;
    }
    Gtk.TextIter buffer_start, buffer_end, cursor_location;
    source_buffer.get_bounds (out buffer_start, out buffer_end);
    source_buffer.remove_tag (grammar_error, buffer_start, buffer_end);
    var cursor = source_buffer.get_insert ();
    source_buffer.get_iter_at_mark (out cursor_location, cursor);

    Gtk.TextIter sentence_start = buffer_start;
    Gtk.TextIter sentence_end = buffer_start;
    while (sentence_end.forward_sentence_end ()) {
        string sentence = strip_markdown (source_buffer.get_text (sentence_start, sentence_end, false));
        if (!cursor_location.in_range (sentence_start, sentence_end)) {
            if (!grammar_correct_sentence_check (sentence)) {
                source_buffer.apply_tag (grammar_error, sentence_start, sentence_end);
            }
        }
        sentence_start = sentence_end;
    }
}

To check the grammar, we want to connect to the buffer changed event.

source_buffer.changed.connect (check_grammar);

And when we type:

KMWriter with Grammar Check

If we add write-good and spell check, we can make our writing look like a Picaso.

Comments