<> <> 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; <> globalBitTable: BitTableLookup.Table _ NIL; <> wordsAddedList: LIST OF ROPE _ NIL; <> 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; <> <<>> <> AuxilliaryWordlistFilenames: LIST OF ROPE; <> BitTableFilename: ROPE; <> insertionInterval: INT _ 60; <> pluralStripping: BOOL _ TRUE; SpellCheckWithin: INTERNAL PROC [s: Selection, toEnd, forwards, wrapAround: BOOL] = { <> 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] = { <> 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] = { <> 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] = { <> UnPrintable: INTERNAL PROC [c: CHAR] RETURNS [unPrintable: BOOL] = INLINE { <> 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] = { <> 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] = { <> present _ globalBitTable # NIL; IF ~present THEN IO.PutF[permMessage, "Word table not yet loaded; wait or restart the Spelling Tool.\n"]; }; SaveInsertions: INTERNAL PROC [] = { <> 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[]; <> 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[]}; END; <> 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]; }; }; EXITS leave => NULL; }; 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]; }; }; EXITS leave => NULL; }; 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]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