Hi Dirk,
I dropped the idea of my own grammar thread. It was way too unstable and no synchronization mechanism to access story and other interfaces in a thread-safe manner known to me.
I am playing with IIdleTask to perform background grammar proofing now. It looks promising. Actually, I already managed to do the real proofing and populate the grammar strand with true results.
The idea with IIdleTask is to process only the first dirty sentence in IIdleTask:: RunTask (), store results to my grammar strand (not using commands as you suggested - works fine), then return control to InDesign. InDesign calls IIdleTask:: RunTask () again and again when idle, so the text gets proofed to the end eventually. Once no more sentences to check, the idle task uninstalls itself.
I made the idle task a part of my grammar strand boss. The grammar strand implementation is non-persistent. It has internal state - markers for dirty/correct/incorrect sections of text - but it does not save to or load from the database.
Grammar strand’s constructor marks entire text dirty and installs the idle task. Each time the grammar strand is notified of the text change via IStrand::Insert(), IStrand::Cut(), or IStrand::Paste(), it uninstalls the idle task, invalidates affected markers, and reinstalls the idle task to (re)start proofing.
However, there are some issues with this approach I still need to address…
My idle task focuses on the main story thread for the time being. I need to add detection of other threads (footnotes, table cells etc.) to grammar proof them too. I don’t estimate many problems with this.
Undo/redo is not working properly. Probably because my grammar strand boss implements IID_IPMPERSIST as every strand has to, but it doesn’t use persistence to save its state at all. If it’ll make InDesign happy, then I’ll save grammar markers, together with grammar settings and grammar engine version. In case grammar settings or engine version is different on load, I’ll invalidate entire strand to make idle task re-proof entire text. A positive side effect would be a quick load of already proofed document in case grammar settings and engine did not change. I shall report on my progress about this.
I have a couple of issues with the UI part too…
I was experimenting with RtMouseText and dynamic menu already. The problem is I do not know how to get coordinates of the right-mouse-click or even better a story and a TextIndex where the click occurred. Probably, I shall add my own event watcher to intercept right mouse click, save the location of the click and use that location later when preparing dynamic menu in RtMouseText. However, rather than using RtMouseText I was thinking of adding my own popup menu: like dynamic spelling does when user right-clicks a misspelled word for seamless user experience. Mindsteam’s grammar checker does this too.
Furthermore, I need to address some issues with adornment refreshing. Consider the following scenario: User types a sentence then stops typing. After 1000ms idle task starts proofing it. However UI installs another delayed idle task to invalidate text and make InDesign refresh it. If grammar results are late, the text is refreshed too soon. I copied the adornment drawing and refreshing mechanism from SpellingPanel and I shall modify it not to rely on timers but rather make it observe changes in the grammar strand.
I am sorry for being so long, but it’s a complex issue. I really appreciate any of your past and future comments. My knowledge of InDesign is still insufficient.
Best regards,
Simon
My grammar engine is practically finished.
This was a real challenge. 
Background grammar processing:
- I must not grammar proof inside idle task, as it blocks other idle tasks badly.
- I've created my own boss to host as many worker threads as there are cores in the computer. All worker threads are spawned with an idle priority. No worker thread can touch ANY of the InDesign interfaces or suffer severe failure.
- Therefore, I've created an idle task for each story. It checks for a free worker thread, finds any pending paragraphs of text within own story, makes a COPY of the text, story UIDRef, and paragraph bounds and spawns a worker thread. When the worker thread is already processing the story's paragraph, the idle task checks if the thread has finished. When finished, idle task copies the result from worker thread to a grammar strand.
- In case the grammar strand receives any story update, it notifies worker thread's boss to either abort the checking of paragraph if the update hit it, or adjust paragraph bounds if the update hit before the paragraph being checked, so the grammar results are inserted to a grammar strand after the proofing is complete at correct location.
It was a bad idea to make the idle task part of my grammar strand boss. InDesign and InCopy had issues with heap corruption in Adobe's code. After I moved idle task (and observer to install/uninstall it) to a text model boss, the problems were gone.
I had to make my grammar strand fully persistent to support undo/redo operation seamlessly. This makes the grammar data to get saved to and loaded from the document too. Since grammar checking is so slow, this is a welcomed speed up when re-opening files. Of course if grammar settings or engine version didn't change meanwhile.
Under a time pressure, I dismissed the idea of having a custom right click menu like SpellingPanel has. I used RtMouseText and dynamic menu technique..
The redrawing of grammar adornmets was solved by using ISubject::ModelChange() on a document. I even took time to implement custom cookie, describing which story and part of text needs a redraw. UI attaches an observer and invalidates the part of text needing a refresh.
Best regards,
Simon