File: SpellingToolImpl.mesa
Last Edited by: Nix, December 2, 1983 11:57 am
DIRECTORY
BitTableLookup USING [Insert, Lookup, Read, Table],
Containers USING [ChildXBound, ChildYBound, Create],
Basics USING [LongNumber, Comparison, BITAND],
FS USING [StreamOpen, Error],
GVSend USING [AddRecipient, AddToItem, Create, Handle, Send, SendFailed, StartSend, StartSendInfo, StartText],
Icons USING [IconFlavor, NewIconFromFile],
IO USING [Backup, Close, EndOfStream, GetChar, int, Put, PutChar, PutF, PutFR, rope, STREAM, text, time],
List USING [UniqueSort],
Menus USING [AppendMenuEntry, ChangeNumberOfLines, CreateEntry, CreateMenu, GetNumberOfLines, Menu, MenuLine, MenuProc, SetLine],
MenusPrivate USING [menuHLeading, menuHSpace],
Process USING [Detach, DisableTimeout, SecondsToTicks, SetTimeout, Yield],
Rope USING [Cat, Compare, Equal, FromRefText, ROPE, Size, Substr, ToRefText],
SpellingCorrection USING [BruteForceCorrection],
SpellingToolDefinition USING [DefinitionButton, InitializeDictionaryServer, FinalizeDictionaryServer, DefinitionStats],
SpellingToolShared USING [FirstWithin, CheckMiddleOfWord, Selection, CorrectTiogaOpsCallWithLocks, MapWordsInSelection, ProcessSelection],
TEditScrolling USING [AutoScroll],
TiogaOps USING [Delete, FetchLooks, GetRope, InsertRope, LastWithin, NoSelection, Root, SaveForPaste, SelectionGrain, SetLooks, SetSelection, StepBackward],
TiogaOpsDefs USING [Location, Ref, Viewer],
TypeScript USING [TS, Create],
UserCredentials USING [Get],
UserProfile USING [Boolean, CallWhenProfileChanges, ListOfTokens, Number, ProfileChangedProc, Token],
VFonts USING [CharWidth, FontHeight, StringWidth],
ViewerBLT USING [ChangeNumberOfLines],
ViewerClasses USING [Viewer],
ViewerEvents USING [EventProc, EventRegistration, RegisterEventProc, UnRegisterEventProc],
ViewerIO USING [CreateViewerStreams],
ViewerOps USING [BlinkIcon, CreateViewer, PaintViewer, SetOpenHeight];
SpellingToolImpl:
CEDAR
MONITOR
IMPORTS Basics, BitTableLookup, Containers, FS, GVSend, Icons, IO, List, Menus, Process, Rope, SpellingCorrection, SpellingToolDefinition, SpellingToolShared, TEditScrolling, TiogaOps, TypeScript, UserCredentials, UserProfile, VFonts, ViewerBLT, ViewerEvents, ViewerIO, ViewerOps =
BEGIN
OPEN SpellingToolShared;
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
Handle for the word list.
globalBitTable: BitTableLookup.Table ← NIL;
List of words that have been added to the word list recently. A background process will write these words to a file and NIL-out this list.
wordsAddedList: LIST OF ROPE ← NIL;
The place for writing permanent messages, e.g. definitions.
permMessage: PUBLIC STREAM;
permMessageIn: STREAM;
reportStatistics: BOOL ← TRUE;
reportCount: INT ← 0;
totalCheckCommands, totalCorrectionsApplied, totalCorrectionsCnt, totalCorrectionsProposedCnt, totalDefinitionsCnt, totalDefinitionsWorkedCount, totalInsertionsCnt, totalMaintainerNotifications, totalWordsCheckedCnt, totalWordsMisspelledCnt, totalWordsProfitablyChecked: INT ← 0;
lastCheckCommands, lastCorrectionsApplied, lastCorrectionsCnt, lastCorrectionsProposedCnt, lastDefinitionsCnt, lastDefinitionsWorkedCount, lastInsertionsCnt, lastMaintainerNotifications, lastWordsCheckedCnt, lastWordsMisspelledCnt, lastWordsProfitablyChecked: INT ← 0;
Parameters settable in User.Profile.
List of the names of the word list files that the user would like merged in to the global word list.
AuxilliaryWordlistFilenames: LIST OF ROPE;
The name of the user's bit table file.
BitTableFilename: ROPE;
The number of seconds between writes to the user's word list file.
insertionInterval: INT ← 60;
Whether or not to strip s's off the ends of misspelled words.
pluralStripping: BOOL ← TRUE;
SpellCheckWithin:
INTERNAL
PROC [s: Selection, toEnd, forwards, wrapAround:
BOOL] = {
Applies the spelling checker within the selection, which searches for the first misspelled word and then highlights it and returns. If toEnd is TRUE, then it ignores whatever the end of the selection is said to be, and searches to the end of the document.
wordStart, wordEnd: TiogaOpsDefs.Location;
loc: TiogaOpsDefs.Location ← s.start;
misspelled: BOOL;
wordsChecked: INT ← 0;
SpellingCheckerProc:
INTERNAL
PROC [word:
REF
TEXT]
RETURNS [misspelled:
BOOL] = {
misspelled ← ~globalBitTable.Lookup[word];
IF misspelled
THEN
misspelled ← HandleMisspelling[word];
wordsChecked ← wordsChecked + 1;
IF
LOOPHOLE[Basics.
BITAND[511,
LOOPHOLE[wordsChecked, Basics.LongNumber].lowbits],
CARDINAL] = 0B
THEN
Process.Yield[];
};
HandleMisspelling:
INTERNAL
PROC [word:
REF
TEXT]
RETURNS [misspelled:
BOOL ←
TRUE] = {
s: INT ← word.length;
lc, sc: CHAR;
IF ~pluralStripping THEN RETURN;
IF s = 1
THEN {
misspelled ← FALSE;
RETURN;
};
IF s <= 4 THEN RETURN;
lc ← word[s-1];
sc ← word[s-2];
IF lc = 's
THEN {
IF sc = 's THEN RETURN;
IF sc = '\'
THEN {
word.length ← s - 2;
misspelled ← ~globalBitTable.Lookup[word];
word.length ← s + 2;
}
ELSE {
word.length ← s - 1;
misspelled ← ~globalBitTable.Lookup[word];
word.length ← s + 1;
};
};
};
Locker:
INTERNAL
PROC [root: TiogaOpsDefs.Ref] = {
TiogaOps.SetSelection[s.viewer, wordStart, wordEnd, char, TRUE, TRUE, primary];
};
IF s.end.where = -1
THEN {
s.end.node ← TiogaOps.StepBackward[s.end.node];
s.end.where ← (TiogaOps.GetRope[s.end.node]).Size[]-1;
};
IF toEnd
THEN {
IF forwards
THEN {
s.end.node ← TiogaOps.LastWithin[TiogaOps.Root[s.start.node]];
s.end.where ← (TiogaOps.GetRope[s.end.node]).Size[]-1;
}
ELSE {
s.start.node ← SpellingToolShared.FirstWithin[TiogaOps.Root[s.start.node]];
s.start.where ← 0;
};
};
[misspelled, wordStart, wordEnd] ← MapWordsInSelection[s.start, s.end, SpellingCheckerProc, forwards];
IF ~misspelled
AND wrapAround
THEN {
IF loc.where = 0
THEN {
s.end.node ← TiogaOps.StepBackward[loc.node];
s.end.where ← (TiogaOps.GetRope[s.end.node]).Size[]-1;
}
ELSE {
s.end.node ← loc.node;
s.end.where ← loc.where - 1;
};
s.start.node ← SpellingToolShared.FirstWithin[TiogaOps.Root[s.end.node]];
s.start.where ← 0;
[misspelled, wordStart, wordEnd] ← MapWordsInSelection[s.start, s.end, SpellingCheckerProc, forwards];
};
totalWordsCheckedCnt ← totalWordsCheckedCnt + wordsChecked;
IF misspelled
THEN {
totalWordsProfitablyChecked ← totalWordsProfitablyChecked + wordsChecked;
totalWordsMisspelledCnt ← totalWordsMisspelledCnt + 1;
CorrectTiogaOpsCallWithLocks[Locker ! TiogaOps.NoSelection => {Locker[NIL]; GOTO noSelection}];
TEditScrolling.AutoScroll[s.viewer];
ProposeCorrections[Rope.Substr[TiogaOps.GetRope[wordStart.node], wordStart.where, wordEnd.where-wordStart.where+1]];
}
ELSE {
IF Menus.GetNumberOfLines[container.menu] # 1
THEN {
Menus.ChangeNumberOfLines[container.menu, 1];
ViewerBLT.ChangeNumberOfLines[container, 1];
ViewerOps.PaintViewer[container, menu];
};
IF wordsChecked = 0
THEN {
IO.PutF[permMessage, "The selection did not contain any words.\n"];
ViewerOps.BlinkIcon[s.viewer, 1, 1];
}
ELSE
IF wordsChecked = 1
THEN
IO.PutF[permMessage, "The selected word is correctly spelled.\n"]
ELSE {
IO.PutF[permMessage, "No misspellings found in %g words.\n", IO.int[wordsChecked]];
ViewerOps.BlinkIcon[s.viewer, 1, 1];
};
};
EXITS
noSelection => NULL;
};
InsertWordOrInsertWordAndSpell:
INTERNAL
PROC [andSpell, forwards, wrapAround:
BOOL] = {
Inserts the selected word into the word list, and if andSpell is TRUE continues searching in the document after the selection for the next misspelled word.
s: Selection;
misspelled: BOOL;
wordStart, wordEnd: TiogaOpsDefs.Location;
Locker:
INTERNAL
PROC [root: TiogaOpsDefs.Ref] = {
WordCollector:
INTERNAL
PROC [word:
REF
TEXT]
RETURNS [stop:
BOOL ←
FALSE] = {
globalBitTable.Insert[word];
totalInsertionsCnt ← totalInsertionsCnt + 1;
IO.PutF[permMessage, "\"%g\" inserted.\n", IO.text[word]];
wordsAddedList ← CONS[Rope.FromRefText[word], wordsAddedList];
};
s ← ProcessSelection[FALSE, FALSE, forwards].s;
[misspelled, wordStart, wordEnd] ← MapWordsInSelection[s.start, s.end, WordCollector, forwards];
};
CorrectTiogaOpsCallWithLocks[Locker ! TiogaOps.NoSelection => GOTO noSelection];
IF insertionInterval = 0
THEN
SaveInsertions[];
IF andSpell
THEN {
IF forwards
THEN {
s.start.node ← s.end.node;
s.start.where ← s.end.where + 1;
}
ELSE {
s.end.node ← s.start.node;
s.end.where ← s.start.where - 1;
};
SpellCheckWithin[s, TRUE, forwards, wrapAround];
};
EXITS
noSelection => IO.PutF[permMessage, "Make a selection.\n"];
};
GetBitFile:
INTERNAL
PROC [fileName:
ROPE]
RETURNS [ succeeded:
BOOLEAN ←
FALSE] = {
Utility for reading in the bit file portion of the word list.
s: STREAM;
IO.PutF[permMessage, "Reading word list from \"%g\"...", IO.rope[fileName]];
s ← FS.StreamOpen[fileName ! FS.Error => GOTO openFailed];
globalBitTable ← BitTableLookup.Read[s];
s.Close[];
succeeded ← TRUE;
IO.Put[permMessage, IO.rope[" done.\n"]];
EXITS
openFailed =>
IO.Put[permMessage, IO.rope[" file could not be read.\n"]];
};
GetToken:
INTERNAL
PROC [s:
STREAM, word:
REF
TEXT]
RETURNS [w:
REF
TEXT] = {
Utility for reading a token from the given stream into the given buffer.
UnPrintable:
INTERNAL
PROC [c:
CHAR]
RETURNS [unPrintable:
BOOL] =
INLINE {
How's this for device independence?
unPrintable ← c IN ['\000 .. '\040];
};
c: CHAR;
w ← word;
WHILE UnPrintable[c ← s.GetChar[]] DO ENDLOOP;
s.Backup[c];
w.length ← w.maxLength;
FOR i:
CARDINAL ← 0, i+1
DO
c ← s.GetChar[ ! IO.EndOfStream => GOTO done];
IF UnPrintable[c] THEN GOTO done;
IF i >= w.maxLength
THEN {
w ← NEW[TEXT[2*w.maxLength+1]];
w.length ← w.maxLength;
};
w[i] ← c;
REPEAT
done => w.length ← i;
ENDLOOP;
MergeWordFile:
INTERNAL
PROC [fileName:
ROPE] = {
Reads in an auxilliary word file and merges the words therein into the word list.
wordFile: STREAM;
IO.PutF[permMessage, "Adding words from file \"%g\"...", IO.rope[fileName]];
wordFile ← FS.StreamOpen[fileName ! FS.Error => GOTO openFailed];
DO
word: REF TEXT ← NEW[TEXT[30]];
word ← GetToken[wordFile, word ! IO.EndOfStream => EXIT];
globalBitTable.Insert[word];
ENDLOOP;
IO.Put[permMessage, IO.rope[" done.\n"]];
EXITS
openFailed =>
IO.Put[permMessage, IO.rope[" file did not exist.\nThis file will be created automatically if you use the Insert command.\n"]];
};
GotBitTable:
INTERNAL
PROC []
RETURNS [present:
BOOLEAN] = {
Checks the state of the world to see if it's OK.
present ← globalBitTable # NIL;
IF ~present
THEN
IO.PutF[permMessage, "Word table not yet loaded; wait or restart the Spelling Tool.\n"];
};
SaveInsertions:
INTERNAL
PROC [] = {
Saves the words the have been recently inserted into the word list in the file that appears first on the list of auxilliary word list files.
IF wordsAddedList #
NIL
THEN {
wordFile: STREAM;
wordFile ← FS.StreamOpen[AuxilliaryWordlistFilenames.first, $append ! FS.Error => TRUSTED {GOTO openFailed}];
FOR w:
LIST
OF
ROPE ← wordsAddedList, w.rest
UNTIL w =
NIL
DO
wordFile.Put[IO.rope[w.first]];
wordFile.PutChar['\n];
ENDLOOP;
wordFile.Close[];
wordsAddedList ← NIL;
EXITS
openFailed =>
IO.PutF[permMessage, "The insertion save file \"%g\" could not be written; insertions not saved.\n", IO.rope[AuxilliaryWordlistFilenames.first]];
[totalDefinitionsCnt, totalDefinitionsWorkedCount] ← SpellingToolDefinition.DefinitionStats[];
IF reportStatistics
THEN
IF (totalCheckCommands # lastCheckCommands)
OR
(totalWordsCheckedCnt # lastWordsCheckedCnt) OR
(totalWordsProfitablyChecked # lastWordsProfitablyChecked) OR
(totalWordsMisspelledCnt # lastWordsMisspelledCnt) OR
(totalInsertionsCnt # lastInsertionsCnt) OR
(totalCorrectionsCnt # lastCorrectionsCnt) OR
(totalCorrectionsProposedCnt # lastCorrectionsProposedCnt) OR
(totalCorrectionsApplied # lastCorrectionsApplied) OR
(totalDefinitionsCnt # lastDefinitionsCnt) OR
(totalDefinitionsWorkedCount # lastDefinitionsWorkedCount) OR
(totalMaintainerNotifications # lastMaintainerNotifications) THEN {
IF reportCount >= 60
THEN {
name, password, stats: Rope.ROPE;
reportCount ← 0;
[name, password] ← UserCredentials.Get[];
stats ← Rope.Cat[
IO.PutFR["\nNumber of searches: %g\nNumber of words checked: %g\nNumber of words profitably checked: %g\nMisspellings found: %g\nInsertions made: %g\n", IO.int[totalCheckCommands], IO.int[totalWordsCheckedCnt], IO.int[totalWordsProfitablyChecked], IO.int[totalWordsMisspelledCnt], IO.int[totalInsertionsCnt]],
IO.PutFR["Words corrected: %g\n", IO.int[totalCorrectionsCnt]],
IO.PutFR["Corrections proposed: %g\nCorrections accepted: %g\nDefinitions requested: %g\nDefinitions given: %g\nNotifications sent: %g\n", IO.int[totalCorrectionsProposedCnt], IO.int[totalCorrectionsApplied], IO.int[totalDefinitionsCnt], IO.int[totalDefinitionsWorkedCount], IO.int[totalMaintainerNotifications]]];
IF ~Mail[name, password, "Nix.pa", "Spelling Tool statistics.", stats] THEN RETURN;
lastCheckCommands ← totalCheckCommands;
lastWordsCheckedCnt ← totalWordsCheckedCnt;
lastWordsProfitablyChecked ← totalWordsProfitablyChecked;
lastWordsMisspelledCnt ← totalWordsMisspelledCnt;
lastInsertionsCnt ← totalInsertionsCnt;
lastCorrectionsCnt ← totalCorrectionsCnt;
lastCorrectionsProposedCnt ← totalCorrectionsProposedCnt;
lastCorrectionsApplied ← totalCorrectionsApplied;
lastDefinitionsCnt ← totalDefinitionsCnt;
lastDefinitionsWorkedCount ← totalDefinitionsWorkedCount;
lastMaintainerNotifications ← totalMaintainerNotifications;
};
reportCount ← reportCount + 1;
};
};
Mail:
INTERNAL
PROC[sender, password, recipient, subject, message:
ROPE]
RETURNS[success:
BOOLEAN ←
FALSE] =
BEGIN
grapevine: GVSend.Handle;
results: GVSend.StartSendInfo;
grapevine ← GVSend.Create[];
scope of GVSend.SendFailed
BEGIN ENABLE GVSend.SendFailed => CONTINUE;
results ← grapevine.StartSend[password, sender, NIL, FALSE];
IF results = ok
THEN {
grapevine.AddRecipient[recipient];
grapevine.StartText[];
grapevine.AddToItem[IO.PutFR["Date: %g\nFrom: %g\nSubject: %g\nTo: %g\n\n%g", IO.time[], IO.rope[sender], IO.rope[subject], IO.rope[recipient], IO.rope[message]]];
grapevine.Send[]};
RETURN[TRUE];
END;
WriteAdditionalWordFile: CONDITION;
MakeAdditionsPermanent:
ENTRY
PROC =
TRUSTED {
ENABLE UNWIND => NULL;
DO
IF insertionInterval > 0
THEN
Process.SetTimeout[@WriteAdditionalWordFile, Process.SecondsToTicks[insertionInterval]]
ELSE
Process.DisableTimeout[@WriteAdditionalWordFile];
SaveInsertions[];
IF globalBitTable = NIL THEN EXIT;
WAIT WriteAdditionalWordFile;
ENDLOOP;
};
CheckSpelling:
ENTRY Menus.MenuProc = {
ENABLE UNWIND => NULL;
s: Selection;
wasExtended: BOOL;
Locker:
INTERNAL
PROC [root: TiogaOpsDefs.Ref] = {
[s, wasExtended] ← ProcessSelection[FALSE, TRUE, TRUE];
};
IF GotBitTable[]
THEN {
CorrectTiogaOpsCallWithLocks[Locker ! TiogaOps.NoSelection => GOTO noSelection];
totalCheckCommands ← totalCheckCommands + 1;
IF mouseButton = blue
AND ~wasExtended
THEN {
s.start.node ← s.end.node;
s.start.where ← s.end.where + 1;
SpellCheckWithin[s, TRUE, TRUE, TRUE];
}
ELSE
SpellCheckWithin[s, FALSE, TRUE, wasExtended];
};
EXITS
noSelection => IO.PutF[permMessage, "Make a selection.\n"];
};
InsertWord:
ENTRY Menus.MenuProc = {
ENABLE UNWIND => NULL;
IF GotBitTable[]
THEN {
InsertWordOrInsertWordAndSpell[mouseButton # red, TRUE, TRUE];
IF mouseButton # red
THEN
totalCheckCommands ← totalCheckCommands + 1;
};
};
Ignore:
ENTRY Menus.MenuProc = {
ENABLE UNWIND => NULL;
s: Selection;
Locker:
INTERNAL
PROC [root: TiogaOpsDefs.Ref] = {
s ← ProcessSelection[FALSE, FALSE, TRUE].s;
s.start.node ← s.end.node;
s.start.where ← s.end.where + 1;
SpellingToolShared.CheckMiddleOfWord[s];
};
IF GotBitTable[]
THEN {
CorrectTiogaOpsCallWithLocks[Locker ! TiogaOps.NoSelection => GOTO noSelection];
totalCheckCommands ← totalCheckCommands + 1;
SpellCheckWithin[s, TRUE, TRUE, TRUE];
};
EXITS
noSelection => IO.PutF[permMessage, "Make a selection.\n"];
};
SaveAdditionsButton:
ENTRY Menus.MenuProc = {
IF GotBitTable[]
THEN {
IF wordsAddedList =
NIL
THEN
IO.PutF[permMessage, "The insertions have already been saved on \"%g\".\n", IO.rope[AuxilliaryWordlistFilenames.first]];
SaveInsertions[];
};
};
theWordCorrected: ROPE ← NIL;
lastWordCorrected: ROPE ← NIL;
correctionBuffer: REF TEXT ← NEW[TEXT[20]];
WordChecker:
INTERNAL
PROC [w:
REF
TEXT]
RETURNS [inTable:
BOOL] = {
inTable ← globalBitTable.Lookup[w];
};
ProposeCorrections:
INTERNAL
PROC [theWord:
ROPE] = {
CompareWords:
INTERNAL
PROC[ref1, ref2:
REF
ANY]
RETURNS [Basics.Comparison] = {
RETURN[Rope.Compare[NARROW[ref1], NARROW[ref2], FALSE]];
};
numCorrections: INT ← 0;
viewerWidth: INTEGER ← container.ww - 6;
oldMenuLines: INTEGER ← Menus.GetNumberOfLines[container.menu];
widthLeft, lineNum: INTEGER;
correctionList: LIST OF ROPE ← NIL;
IF theWord =
NIL
THEN {
IO.PutF[permMessage, "No word selected, so no correction done.\n"];
RETURN;
};
totalCorrectionsCnt ← totalCorrectionsCnt + 1;
lastWordCorrected ← theWord;
theWordCorrected ← theWord;
[correctionList, correctionBuffer] ←
SpellingCorrection.BruteForceCorrection[theWord, WordChecker, correctionBuffer];
TRUSTED {correctionList ← LOOPHOLE[List.UniqueSort[LOOPHOLE[correctionList], CompareWords]];};
IF correctionList #
NIL
THEN {
correctionList ← CONS[Rope.Cat[theWord, " --> "], correctionList];
};
lineNum ← 0;
widthLeft ← 0;
Menus.ChangeNumberOfLines[container.menu, 2];
IF correctionList =
NIL
THEN {
lineNum ← 1;
Menus.SetLine[container.menu, 1, NIL];
Menus.AppendMenuEntry[line: 1, menu: container.menu,
entry: Menus.CreateEntry[name: "No corrections for this word.", proc: NoCorrectionButton]];
IO.PutF[permMessage, "\"%g\" may be misspelled; sorry, no corrections.\n", IO.rope[theWordCorrected]];
}
ELSE {
FOR c:
LIST
OF
ROPE ← correctionList, c.rest
UNTIL c =
NIL
DO
width: INT ← VFonts.StringWidth[c.first];
IF width > widthLeft
AND widthLeft < viewerWidth
THEN {
IF ~(lineNum+2
IN Menus.MenuLine)
THEN {
IO.PutF[permMessage, "There are more corrections, but they can't fit in the menu.\n"];
EXIT;
};
lineNum ← lineNum + 1;
widthLeft ← viewerWidth - MenusPrivate.menuHLeading;
Menus.SetLine[container.menu, lineNum, NIL];
};
Menus.AppendMenuEntry[line: lineNum, menu: container.menu,
entry: Menus.CreateEntry[name: c.first, clientData: IF c = correctionList THEN theWord ELSE c.first, proc: IF c = correctionList THEN ApplyInsertionButton ELSE ApplyCorrectionButton]];
widthLeft ← widthLeft - width - MenusPrivate.menuHSpace;
numCorrections ← numCorrections + 1;
ENDLOOP;
IO.PutF[permMessage, "\"%g\" may be misspelled; %g correction buttons above.\n", IO.rope[theWordCorrected], IO.int[numCorrections-1]];
};
IF lineNum+1 # oldMenuLines
THEN
ViewerBLT.ChangeNumberOfLines[container, lineNum+1];
totalCorrectionsProposedCnt ← totalCorrectionsProposedCnt + numCorrections - 1;
ViewerOps.PaintViewer[container, menu];
};
ApplyCorrectionButton:
ENTRY Menus.MenuProc = {
ENABLE UNWIND => NULL;
theWord: ROPE ← NIL;
correction: ROPE ← NARROW[clientData];
wordCount: INT ← 0;
s: Selection;
Locker:
INTERNAL
PROC [root: TiogaOpsDefs.Ref] =
TRUSTED {
ExtractOneWord:
INTERNAL
PROC [word:
REF
TEXT]
RETURNS [stop:
BOOLEAN ←
FALSE] =
TRUSTED {
stop ← wordCount > 0;
IF wordCount = 0
THEN
theWord ← Rope.FromRefText[word];
wordCount ← wordCount + 1;
};
s ← ProcessSelection[FALSE,TRUE, TRUE].s;
[] ← MapWordsInSelection[s.start, s.end, ExtractOneWord, TRUE];
IF theWord =
NIL
THEN
IO.PutF[permMessage, "No word has been selected.\n"];
};
MakeCorrection:
INTERNAL
PROC [root: TiogaOpsDefs.Ref] = {
looks: ROPE ← TiogaOps.FetchLooks[s.start.node, s.start.where];
TiogaOps.SaveForPaste[];
TiogaOps.Delete[];
TiogaOps.SetLooks[looks];
TiogaOps.InsertRope[correction];
s.end.where ← s.start.where + correction.Size[] - 1;
TiogaOps.SetSelection[viewer: s.viewer, start: s.start, end: s.end, pendingDelete: TRUE];
};
CorrectTiogaOpsCallWithLocks[Locker ! TiogaOps.NoSelection => {
IO.PutF[permMessage, "No word is selected, so no correction can be applied.\n"];
GOTO leave;}];
IF wordCount = 0
THEN {
IO.PutF[permMessage, "No word is selected, so no correction can be applied.\n"];
RETURN;
};
IF wordCount > 1
THEN {
IO.PutF[permMessage, "More than one word selected, so no correction can be applied.\n"];
RETURN;
};
IF ~(Rope.Equal[theWord, lastWordCorrected]
OR Rope.Equal[theWord, theWordCorrected])
THEN {
IO.PutF[permMessage, "Selected word, \"%g\", is not the same as \"%g\".\n", , IO.rope[theWord], IO.rope[theWordCorrected]];
RETURN;
};
totalCorrectionsApplied ← totalCorrectionsApplied + 1;
IF ~Rope.Equal[theWord, correction]
THEN
CorrectTiogaOpsCallWithLocks[MakeCorrection ! TiogaOps.NoSelection => {
IO.PutF[permMessage, "There is no selection, so no correction can be applied.\n"];
GOTO leave;}];
lastWordCorrected ← correction;
IF mouseButton = blue
THEN {
s ← ProcessSelection[FALSE,FALSE, TRUE].s;
s.start.node ← s.end.node;
s.start.where ← s.end.where + 1;
IF GotBitTable[]
THEN {
totalCheckCommands ← totalCheckCommands + 1;
SpellCheckWithin[s, TRUE, TRUE, TRUE];
};
};
};
ApplyInsertionButton:
ENTRY Menus.MenuProc = {
ENABLE UNWIND => NULL;
theWord: ROPE ← NIL;
correction: ROPE ← NARROW[clientData];
wordCount: INT ← 0;
s: Selection;
Locker:
INTERNAL
PROC [root: TiogaOpsDefs.Ref] =
TRUSTED {
ExtractOneWord:
INTERNAL
PROC [word:
REF
TEXT]
RETURNS [stop:
BOOLEAN ←
FALSE] =
TRUSTED {
stop ← wordCount > 0;
IF wordCount = 0
THEN
theWord ← Rope.FromRefText[word];
wordCount ← wordCount + 1;
};
s ← ProcessSelection[FALSE,TRUE, TRUE].s;
[] ← MapWordsInSelection[s.start, s.end, ExtractOneWord, TRUE];
IF theWord =
NIL
THEN
IO.PutF[permMessage, "No word has been selected.\n"];
};
MakeCorrection:
INTERNAL
PROC [root: TiogaOpsDefs.Ref] = {
looks: ROPE ← TiogaOps.FetchLooks[s.start.node, s.start.where];
TiogaOps.SaveForPaste[];
TiogaOps.Delete[];
TiogaOps.SetLooks[looks];
TiogaOps.InsertRope[correction];
s.end.where ← s.start.where + correction.Size[] - 1;
TiogaOps.SetSelection[viewer: s.viewer, start: s.start, end: s.end, pendingDelete: TRUE];
};
CorrectTiogaOpsCallWithLocks[Locker ! TiogaOps.NoSelection => {
IO.PutF[permMessage, "No word is selected, so no correction can be applied.\n"];
GOTO leave;}];
IF wordCount = 0
THEN {
IO.PutF[permMessage, "No word is selected, so no correction can be applied.\n"];
RETURN;
};
IF wordCount > 1
THEN {
IO.PutF[permMessage, "More than one word selected, so no correction can be applied.\n"];
RETURN;
};
IF ~(Rope.Equal[theWord, lastWordCorrected]
OR Rope.Equal[theWord, theWordCorrected])
THEN {
IO.PutF[permMessage, "Selected word, \"%g\", is not the same as \"%g\".\n", , IO.rope[theWord], IO.rope[theWordCorrected]];
RETURN;
};
totalCorrectionsApplied ← totalCorrectionsApplied + 1;
IF ~Rope.Equal[theWord, correction]
THEN
CorrectTiogaOpsCallWithLocks[MakeCorrection ! TiogaOps.NoSelection => {
IO.PutF[permMessage, "There is no selection, so no correction can be applied.\n"];
GOTO leave;}];
lastWordCorrected ← correction;
globalBitTable.Insert[Rope.ToRefText[theWordCorrected]];
totalInsertionsCnt ← totalInsertionsCnt + 1;
IO.PutF[permMessage, "\"%g\" inserted.\n", IO.rope[theWordCorrected]];
wordsAddedList ← CONS[theWordCorrected, wordsAddedList];
IF mouseButton = blue
THEN {
s ← ProcessSelection[FALSE,FALSE, TRUE].s;
s.start.node ← s.end.node;
s.start.where ← s.end.where + 1;
IF GotBitTable[]
THEN {
totalCheckCommands ← totalCheckCommands + 1;
SpellCheckWithin[s, TRUE, TRUE, TRUE];
};
};
};
NoCorrectionButton:
ENTRY Menus.MenuProc = {
ENABLE UNWIND => NULL;
IO.PutF[permMessage, "\"%g\" may be misspelled; no similar words are in the list.\n", IO.rope[theWordCorrected]];
};
container: ViewerClasses.Viewer ← NIL;
registration: ViewerEvents.EventRegistration;
QuitSpellTool:
ENTRY ViewerEvents.EventProc = {
ENABLE UNWIND => NULL;
IF viewer = container
AND event = destroy
THEN {
SpellingToolDefinition.FinalizeDictionaryServer[];
globalBitTable ← NIL;
container ← NIL;
ViewerEvents.UnRegisterEventProc[registration, destroy];
};
};
BuildSpellingTool:
ENTRY
PROC [withTypeScript:
BOOL ←
FALSE] = {
ENABLE UNWIND => NULL;
menu: Menus.Menu ← NIL;
ymax: INTEGER ← 0;
emWidth: INTEGER ← VFonts.CharWidth['M];
emHeight: INTEGER ← VFonts.FontHeight[];
icon: Icons.IconFlavor;
ts: TypeScript.TS;
menu ← Menus.CreateMenu[lines: 1];
Menus.AppendMenuEntry
[line: 0,menu: menu, entry: Menus.CreateEntry[name: "Check", proc: CheckSpelling]];
Menus.AppendMenuEntry
[line: 0,menu: menu, entry: Menus.CreateEntry[name: "Insert", proc: InsertWord]];
Menus.AppendMenuEntry
[line: 0,menu: menu, entry: Menus.CreateEntry[name: "Definition", proc: SpellingToolDefinition.DefinitionButton]];
icon ← Icons.NewIconFromFile["SpellingTool.icons", 0];
container ← Containers.Create[[name: "Spelling Tool", iconic: TRUE, column: right, menu: menu, scrollable: FALSE, icon: icon]];
registration ← ViewerEvents.RegisterEventProc[QuitSpellTool, destroy];
ts ← TypeScript.Create[info: [parent: container, border: FALSE, scrollable: TRUE, wy: 2], paint: FALSE];
[permMessageIn, permMessage] ← ViewerIO.CreateViewerStreams[name: "Definitions", viewer: ts, editedStream: FALSE];
TRUSTED {
Process.Detach[FORK SpellingToolDefinition.InitializeDictionaryServer[permMessage]];
};
ViewerOps.SetOpenHeight[container, 6*emHeight + 8];
Containers.ChildXBound[container, ts];
Containers.ChildYBound[container, ts];
ViewerOps.PaintViewer[container, all];
};
InitializeProfile:
ENTRY UserProfile.ProfileChangedProc = {
ENABLE UNWIND => NULL;
AuxilliaryWordlistFilenames ← UserProfile.ListOfTokens["SpellingTool.Wordlists", LIST["SpellingTool.Wordlist"]];
BitTableFilename ← UserProfile.Token["SpellingTool.BitTable", "[Indigo]<SpellingToolData>SpellingTool.BitTable"];
insertionInterval ← UserProfile.Number["SpellingTool.InsertionInterval", 60];
pluralStripping ← UserProfile.Boolean["SpellingTool.PluralStripping", TRUE];
[] ← GetBitFile[BitTableFilename];
FOR f:
LIST
OF
ROPE ← AuxilliaryWordlistFilenames, f.rest
UNTIL f =
NIL
DO
MergeWordFile[f.first];
ENDLOOP;
BROADCAST WriteAdditionalWordFile;
};
BuildSpellingTool[];
UserProfile.CallWhenProfileChanges[InitializeProfile];
TRUSTED {Process.Detach[FORK MakeAdditionsPermanent[]];};
END.
CHANGE LOG
Created by Nix on September 8, 1983 11:17 am