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 δFile: SpellingToolImpl.mesa Last Edited by: Nix, December 2, 1983 11:57 am Handle for the word list. 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. The place for writing permanent messages, e.g. definitions. 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. The name of the user's bit table file. The number of seconds between writes to the user's word list file. Whether or not to strip s's off the ends of misspelled words. 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. 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. Utility for reading in the bit file portion of the word list. Utility for reading a token from the given stream into the given buffer. How's this for device independence? Reads in an auxilliary word file and merges the words therein into the word list. Checks the state of the world to see if it's OK. 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. scope of GVSend.SendFailed end scope Κ|˜– "Cedar" stylešœ™J™.—unitšΟk ˜ Icodešœœ˜3Lšœ œ$˜4Lšœœœ˜.Lšœœ˜Lšœœb˜nLšœœ˜*LšœœMœ˜iLšœœ˜Lšœœv˜Lšœ œ˜.Lšœœ=˜JLšœœ$œ˜MLšœœ˜0Lšœœ[˜wLšœœr˜ŠLšœœ˜"Lšœ œŽ˜œLšœ œ˜+Lšœ œœ ˜Lšœœ˜Lšœ œT˜eLšœœ&˜2Lšœ œ˜&Lšœœ ˜Lšœ œH˜ZLšœ œ˜%Lšœ œ7˜FJ˜—šΟbœœ˜Lšœ%œœΩ˜š—Lš˜Lšœ˜Lšœœœ˜Lšœœœœ˜˜LšΟc™—Lšœ(œ˜,˜LšŸ‹™‹—Jš œœœœœ˜#˜Jšœ;™;—Jšœ œœ˜Jšœœ˜J˜Lšœœœ˜Lšœ œ˜Lšœœ˜—Lšœ„œ˜ŒJ˜šœ$™$J™Jšœd™d—Lšœœœœ˜*˜LšŸ&™&—Lšœœ˜˜LšŸB™B—Lšœœ˜˜LšŸ=™=—Lšœœœ˜J˜J˜J˜J˜šΟnœœœ-œ˜UJšœ€™€J˜*J˜%Jšœ œ˜Jšœ Οs‘œ˜J˜š œœœœœœœ˜RJ˜*šœ ˜J˜%—J˜ š œœœœ,œ˜fJ˜—J˜J˜—š œœœœœœœœ˜WJšœœ˜Jšœœ˜ Jšœœœ˜ šœœ˜Jšœ œ˜Jšœ˜J˜—Jšœœœ˜J˜J˜šœ œ˜Jšœ œœ˜šœ œ˜J˜J˜*J˜J˜—šœ˜J˜J˜*J˜J˜—J˜—J˜—šΠbnœœœ˜2Jšœ:œœ ˜OJ˜J˜—šœœ˜Jšœ/˜/Jšœ6˜6J˜—šœœ˜šœ œ˜J˜>J˜6J˜—šœ˜J˜KJ˜J˜—J˜—J˜fšœ œ œ˜$šœœ˜Jšœ-˜-Jšœ6˜6J˜—šœ˜Jšœ˜Jšœ˜J˜—J˜IJ˜J˜fJ˜—J˜;šœ œ˜JšœI˜IJšœ6˜6JšœFœœ˜_J˜$J˜tJ˜—šœ˜šœ,œ˜4Lšœ-˜-Lšœ,˜,Lšœ'˜'L˜—šœœ˜JšœA˜CJ˜$J˜—šœœœ˜Jšœ?˜A—šœ˜Jšœ;œ˜SJ˜$J˜—J˜—š˜Jšœœ˜—J˜J˜—J˜š œœœ"œ˜XJšœ›™›J˜J˜ Jšœ œ˜J˜*J˜š’œœœ˜2š  œœœœœœœœ˜NJ˜J˜-Jšœ)œ ˜:Jšœœ)˜>J˜J˜—Jšœœœ˜/J˜`J˜—Jšœ>œ˜Pšœ˜J˜—šœ œ˜šœ œ˜J˜J˜ J˜—šœ˜J˜J˜ J˜—Jšœœ˜0J˜—š˜Jšœœ*˜;—J˜J˜J˜—š  œœœ œœœœ˜TJšœ=™=J˜Jšœœ˜ Jšœ7œ˜LJšœœœ œ ˜;J˜(J˜ Jšœ œ˜Jšœœ˜)š˜šœ˜Jšœœ%˜;——J˜J˜—š œœœœœœœœœ˜MJšœH™Hš  œœœœœœœ˜KJšœ#™#Jšœœ˜$J˜—Jšœœ˜J˜ Jšœœœ˜/J˜ J˜šœœ ˜Jšœœœ˜.Jšœœœ˜!šœœ˜Jšœœœ˜J˜J˜—J˜ š˜J˜—Jšœ˜—˜J˜—J˜—š  œœœ œ˜1JšœQ™QLšœ œ˜Jšœ7œ˜LLšœ œœ œ ˜Aš˜Lš œœœœœ˜Lšœ!œœ˜9L˜Lšœ˜—Jšœœ˜)š˜˜ Jšœœi˜——L˜L˜L˜—š   œœœœ œ˜œ˜PJšœ,˜,šœœœ˜-Jšœ˜J˜ Jšœœœœ˜&J˜—šœ˜Jšœœœ˜.—J˜—š˜Jšœœ*˜;—J˜J˜—šž œœ˜$Lšœœœ˜šœœ˜Jšœ2œœ˜>šœœ˜Jšœ,˜,—J˜—J˜J˜J˜—L˜šžœœ˜ Lšœœœ˜Jšœ ˜ š’œœœ˜2Jšœœœœ˜+Jšœ˜J˜ J˜(J˜—šœœ˜Jšœ>œ˜PJšœ,˜,Jšœœœœ˜&J˜—š˜Jšœœ*˜;—J˜J˜J˜—šžœœ˜-šœœ˜šœœ˜LšœJœ*˜x—Lšœ˜L˜—L˜L˜L˜—Lšœœœ˜Lšœœœ˜Lš œœœœœ˜+L˜š  œœœœœœ œ˜DL˜#L˜L˜—š’œœœ œ˜5š   œœœ œœœ˜PLšœœœœ˜8L˜—Lšœœ˜Lšœ œ˜(Lšœœ*˜?Lšœœ˜Lš œœœœœ˜#šœ œœ˜LšœA˜CLšœ˜L˜—L˜.L˜L˜šœ%˜%LšœP˜P—Lšœœœ#˜^šœœœ˜Lšœœ-˜BL˜—Lšœ ˜ Lšœ˜Lšœ-˜-šœœœ˜L˜ Lšœ!œ˜&Lšœ‘˜‘LšœIœ˜fL˜—šœ˜š œœœœœœ˜=Lšœœ˜)šœœœ˜7šœ œœ˜(LšœT˜VLšœ˜L˜—L˜Lšœ4˜4Lšœ'œ˜,L˜—Lš œpœœ œœœœ˜τLšœ8˜8Lšœ$˜$Lšœ˜—LšœOœœ˜†L˜—šœ˜ Lšœ4˜4—LšœO˜OLšœ'˜'Lšœ˜L˜L˜—šžœœ˜/Lšœœœ˜Lšœ œœ˜Lšœ œœ ˜&Lšœ œ˜Jšœ ˜ š’œœœœ˜:š œœœœœœœœœ˜ZL˜šœœ˜L˜!—L˜L˜—Lšœœœœ˜)Jšœ9œ˜?šœ œœ˜Lšœ3˜5—L˜—š œœœ˜:Lšœœ4˜?L˜L˜Lšœ˜Lšœ ˜ L•StartOfExpansionφ[viewer: ViewerClasses.Viewer, start: TiogaOpsDefs.Location, end: TiogaOpsDefs.Location, level: TiogaOpsDefs.SelectionGrain _ char, caretBefore: BOOL _ TRUE, pendingDelete: BOOL _ FALSE, which: TiogaOpsDefs.WhichSelection _ primary]šœ4˜4LšœSœ˜YLšœ˜L˜—šœ?˜?LšœN˜PLšœ ˜—šœœ˜LšœN˜PLšœ˜L˜—šœœ˜LšœV˜XLšœ˜L˜—šœ*œ(œ˜\LšœLœœ˜{Lšœ˜L˜—L˜6šœ"˜(šœG˜GLšœP˜RLšœ ˜——Lšœ˜šœœ˜Jšœœœœ˜*Jšœ˜Jšœ ˜ šœœ˜Jšœ,˜,Jšœœœœ˜&J˜—J˜—š˜Lšœ œ˜—L˜L˜L˜—šžœœ˜.Lšœœœ˜Lšœ œœ˜Lšœ œœ ˜&Lšœ œ˜Jšœ ˜ š’œœœœ˜:š œœœœœœœœœ˜ZL˜šœœ˜L˜!—L˜L˜—Lšœœœœ˜)Jšœ9œ˜?šœ œœ˜Lšœ3˜5—L˜—š œœœ˜:Lšœœ4˜?L˜L˜Lšœ˜Lšœ ˜ L–φ[viewer: ViewerClasses.Viewer, start: TiogaOpsDefs.Location, end: TiogaOpsDefs.Location, level: TiogaOpsDefs.SelectionGrain _ char, caretBefore: BOOL _ TRUE, pendingDelete: BOOL _ FALSE, which: TiogaOpsDefs.WhichSelection _ primary]šœ4˜4LšœSœ˜YLšœ˜L˜—šœ?˜?LšœN˜PLšœ ˜—šœœ˜LšœN˜PLšœ˜L˜—šœœ˜LšœV˜XLšœ˜L˜—šœ*œ(œ˜\LšœLœœ˜{Lšœ˜L˜—L˜6šœ"˜(šœG˜GLšœP˜RLšœ ˜——Lšœ˜Jšœ8˜8J˜,Jšœ)œ˜FJšœœ#˜8šœœ˜Jšœœœœ˜*Jšœ˜Jšœ ˜ šœœ˜Jšœ,˜,Jšœœœœ˜&J˜—J˜—š˜Lšœ œ˜—L˜L˜L˜—šžœœ˜,Lšœœœ˜LšœTœ˜qL˜L˜L˜—Lšœ"œ˜&Lšœ-˜-L˜šž œœ˜/Lšœœœ˜šœœœ˜0Jšœ2˜2Lšœœ˜Lšœ œ˜Lšœ8˜8L˜—L˜—˜L˜—š  œœœœœ˜@Lšœœœ˜Jšœœ˜Jšœœ˜Jšœ œ˜(Jšœ œ˜(J˜Jšœœ˜J˜Jšœ"˜"˜JšœS˜S—˜JšœQ˜Q—˜Jšœr˜r—J˜6Jšœ>œ)œ˜JšœF˜FJšœ9œœœ˜hJšœkœ˜ršœ˜ LšœœA˜TLšœ˜—J˜J˜3J˜&J˜&J˜J˜&J˜J˜—L˜šžœœ#˜;Lšœœœ˜LšœQœ˜pL˜qL˜MLšœFœ˜LJšœ"˜"šœœœœ'œœœ˜KJ˜Jšœ˜—Jš œ˜"J˜L˜—L˜L˜6Lšœœ˜9L˜procšœ˜Kšœ˜ Kšœ,˜,——…—_~€ή