DIRECTORY Atom USING [MakeAtom], BitTableLookup USING [Failed, Insert, Lookup, Read, Table], Commander USING [CommandProc, Register], CommandTool USING [CurrentWorkingDirectory], Containers USING [ChildXBound, ChildYBound, Create], Basics USING [LongNumber, Comparison, BITAND], FS USING [ComponentPositions, Error, ExpandName, StreamOpen], GVSend USING [AddRecipient, AddToItem, Create, Handle, Send, SendFailed, StartSend, StartSendInfo, StartText], Icons USING [IconFlavor, NewIconFromFile], IO USING [Backup, CharClass, Close, EndOf, EndOfStream, GetChar, GetTokenRope, int, Put, PutChar, PutF, PutFR, PutRope, PutText, RIS, rope, RopeFromROS, ROS, SkipWhitespace, STREAM, time, TIS], List USING [UniqueSort], Menus USING [AppendMenuEntry, ChangeNumberOfLines, CreateEntry, CreateMenu, GetNumberOfLines, Menu, MenuEntry, MenuLine, MenuProc, MouseButton, ReplaceMenuEntry, SetLine], Process USING [Detach, DisableTimeout, SecondsToTicks, SetTimeout, Yield], Rope USING [Cat, Compare, Equal, Fetch, Find, FromRefText, Length, Replace, ROPE, Size, Substr], SpellingCorrection USING [BruteForceCorrection], SpellingToolDefinition USING [DefinitionButton, InitializeDictionaryServer, FinalizeDictionaryServer, DefinitionStats], SpellingToolShared USING [FirstWithin, CheckMiddleOfWord, Selection, CorrectTiogaOpsCallWithLocks, MapWordsInSelection, ProcessSelection], TEditScrolling USING [AutoScroll], TEditOps USING [WorkingDirectoryFromViewer], TextEdit USING [GetProp, PutProp], TextNode USING [Ref], TiogaOps USING [Delete, FetchLooks, GetProp, GetRope, InsertRope, LastWithin, NoSelection, Ref, 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, ViewerEvent], ViewerIO USING [CreateViewerStreams], ViewerOps USING [AddProp, BlinkIcon, FetchProp, PaintViewer, SetOpenHeight], ViewerPrivate USING [menuHLeading, menuHSpace], VM USING [Age, Touch]; SpellingToolImpl: CEDAR MONITOR IMPORTS Atom, Basics, BitTableLookup, Commander, CommandTool, Containers, FS, GVSend, Icons, IO, List, Menus, Process, Rope, SpellingCorrection, SpellingToolDefinition, SpellingToolShared, TEditOps, TEditScrolling, TextEdit, TiogaOps, TypeScript, UserCredentials, UserProfile, VFonts, ViewerBLT, ViewerEvents, ViewerIO, ViewerOps, VM = BEGIN OPEN SpellingToolShared; ROPE: TYPE = Rope.ROPE; RopeList: TYPE = LIST OF ROPE; STREAM: TYPE = IO.STREAM; Viewer: TYPE = ViewerClasses.Viewer; ViewerList: TYPE = LIST OF Viewer; LocalDataList: TYPE = LIST OF LocalData; LocalData: TYPE = REF LocalDataRep; LocalDataRep: TYPE = RECORD [ BitTableFilename: ROPE _ NIL, globalAuxilliaryFileWordlistNames: RopeList _ NIL, localAuxilliaryFileWordlistNamesRope: ROPE _ NIL, wDir: ROPE _ NIL, toRoot: TiogaOps.Ref _ NIL, auxilliaryWordlists: WordlistList _ NIL, bitTable: BitTableLookup.Table _ NIL, users: ViewerList _ NIL ]; WordlistList: TYPE = LIST OF Wordlist; Wordlist: TYPE = REF WordlistRep; WordlistRep: TYPE = RECORD [ name: ROPE, homeType: WordlistHomeType, toRoot: TiogaOps.Ref _ NIL, tnRoot: TextNode.Ref _ NIL, empty: BOOL _ TRUE, localDatas: LocalDataList _ NIL, wordsAddedList: RopeList _ NIL, menuEntry: Menus.MenuEntry _ NIL ]; WordlistHomeType: TYPE = {file, doc}; installationDirectory: ROPE _ CommandTool.CurrentWorkingDirectory[]; localAuxilliaryWordlistNamesProperty: ATOM = $Wordlists; localWordlistProperty: ATOM = $Wordlist; localListName: ROPE = "(local)"; viewerLocalDataKey: ATOM _ Atom.MakeAtom[IO.PutFR["SpellingTool (started %g) Viewer to LocalData", IO.time[]]]; spellingToolIcon: Icons.IconFlavor = Icons.NewIconFromFile["SpellingTool.icons", 0]; localDataCache: LocalDataList _ NIL; wordlists: WordlistList _ NIL; dirty: BOOL _ FALSE; lastRoot: TiogaOps.Ref _ NIL; toolExists: BOOL _ FALSE; toolMenu: Menus.Menu _ Menus.CreateMenu[lines: 1]; localInsertMenud: BOOL _ FALSE; localInsertMenuEntry: Menus.MenuEntry _ NIL; permMessage: PUBLIC STREAM; permMessageIn: STREAM; reportStatistics: BOOL _ FALSE; reportStatisticsTo: ROPE _ "Nix.pa"; 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; globalAuxilliaryWordlistNames: RopeList; globalAuxilliaryFileWordlistNames: RopeList; localInEvery: BOOL; 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; localData: LocalData _ GetLocalData[s]; SpellingCheckerProc: INTERNAL PROC [word: REF TEXT] RETURNS [misspelled: BOOL] = { misspelled _ ~localData.bitTable.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 _ ~localData.bitTable.Lookup[word]; word.length _ s + 2; } ELSE { word.length _ s - 1; misspelled _ ~localData.bitTable.Lookup[word]; word.length _ s + 1; }; }; }; Locker: INTERNAL PROC [root: TiogaOpsDefs.Ref] = { TiogaOps.SetSelection[s.viewer, wordStart, wordEnd, char, TRUE, TRUE, primary]; }; IF NOT Valid[localData] THEN { permMessage.PutRope["Can't check.\n"]; RETURN}; lastRoot _ TiogaOps.Root[s.start.node]; VM.Touch[localData.bitTable.vmHandle.interval]; 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[localData, 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]; }; }; VM.Age[localData.bitTable.vmHandle.interval]; EXITS noSelection => NULL; }; InsertWordOrInsertWordAndSpell: INTERNAL PROC [wl: Wordlist, andSpell, forwards, wrapAround: BOOL] = { s: Selection; localData: LocalData; misspelled: BOOL; wordStart, wordEnd: TiogaOpsDefs.Location; auxilliaryWordlistFilename: ROPE _ NIL; Locker: INTERNAL PROC [root: TiogaOpsDefs.Ref] = { WordCollector: INTERNAL PROC [word: REF TEXT] RETURNS [stop: BOOL _ FALSE] = { AddWord[wl, word, Rope.FromRefText[word]]; }; s _ ProcessSelection[FALSE, FALSE, forwards].s; localData _ GetLocalData[s]; IF Valid[localData] THEN [misspelled, wordStart, wordEnd] _ MapWordsInSelection[s.start, s.end, WordCollector, forwards] ELSE permMessage.PutRope["Can't check.\n"]; }; CorrectTiogaOpsCallWithLocks[Locker ! TiogaOps.NoSelection => GOTO noSelection]; IF NOT Valid[localData] THEN RETURN; 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"]; }; LocalListName: PROC [root: TiogaOps.Ref] RETURNS [name: ROPE] = { name _ IO.PutFR["^%g", [integer[LOOPHOLE[root]]]]; }; HomeTypeFromName: PROC [name: ROPE] RETURNS [ht: WordlistHomeType] = { ht _ IF name.Fetch[0]='^ THEN doc ELSE file}; Valid: INTERNAL PROC [localData: LocalData] RETURNS [valid: BOOL] = { valid _ localData # NIL; }; GetLocalData: INTERNAL PROC [s: Selection] RETURNS [localData: LocalData] = { root: TiogaOps.Ref = TiogaOps.Root[s.start.node]; rootName: ROPE = LocalListName[root]; localAuxilliaryFileWordlistNamesRope: ROPE; auxilliaryWordlists: WordlistList _ NIL; docLocalWordlist: Wordlist; localListIncludesDocLocal, listComputed, includeDocLocalWordlist: BOOL _ FALSE; useRoot: TiogaOps.Ref _ NIL; WholeList: INTERNAL PROC RETURNS [wll: WordlistList] = INLINE { IF NOT listComputed THEN { auxilliaryWordlists _ ParseToList[wDir, localAuxilliaryFileWordlistNamesRope, globalAuxilliaryFileWordlistNames, includeDocLocalWordlist, docLocalWordlist]; listComputed _ TRUE}; wll _ auxilliaryWordlists}; wDir: ROPE _ TEditOps.WorkingDirectoryFromViewer[s.viewer]; ValidCache: INTERNAL PROC RETURNS [valid: BOOL] = { valid _ Rope.Equal[localData.BitTableFilename, BitTableFilename, FALSE] AND ( (Rope.Equal[ localData.localAuxilliaryFileWordlistNamesRope, localAuxilliaryFileWordlistNamesRope, FALSE] AND Rope.Equal[localData.wDir, wDir, FALSE] AND RopeListEqual[ localData.globalAuxilliaryFileWordlistNames, globalAuxilliaryFileWordlistNames] AND localData.toRoot = useRoot) OR WordlistListEqual[WholeList[], localData.auxilliaryWordlists]); }; oldLocalData: LocalData = localData _ NARROW[ViewerOps.FetchProp[s.viewer, viewerLocalDataKey]]; UseIt: INTERNAL PROC RETURNS [ld: LocalData] = { ViewerOps.AddProp[s.viewer, viewerLocalDataKey, ld _ localData]; IF oldLocalData # NIL THEN NoteNonUse[oldLocalData, s.viewer]; IF localData # NIL THEN localData.users _ CONS[s.viewer, localData.users]; }; [localAuxilliaryFileWordlistNamesRope, localListIncludesDocLocal] _ Remove[NARROW[TiogaOps.GetProp[root, localAuxilliaryWordlistNamesProperty]], localListName]; includeDocLocalWordlist _ (localListIncludesDocLocal OR localInEvery) AND NOT (docLocalWordlist _ FindWordlist[doc, rootName, rootName, root]).empty; IF includeDocLocalWordlist THEN useRoot _ root; IF localData = NIL OR (NOT Valid[localData]) OR (NOT ValidCache[]) THEN { FOR ldc: LocalDataList _ localDataCache, ldc.rest WHILE ldc # NIL DO localData _ ldc.first; IF Valid[localData] AND ValidCache[] THEN RETURN [UseIt[]]; ENDLOOP; [] _ WholeList[]; WaitTillClean[]; localData _ ComputeLocalData[wDir, localAuxilliaryFileWordlistNamesRope, auxilliaryWordlists]; [] _ UseIt[]; }; }; ParseToList: INTERNAL PROC [wDir, rope: ROPE, globals: RopeList, includeDocLocalWordlist: BOOL, docLocalWordlist: Wordlist] RETURNS [list: WordlistList] = { Add: PROC [x: Wordlist, l: WordlistList] RETURNS [new: WordlistList] = { IF l = NIL THEN RETURN [LIST[x]]; SELECT Rope.Compare[x.name, l.first.name, FALSE] FROM less => RETURN [CONS[x, l]]; equal => RETURN [l]; greater => RETURN [CONS[l.first, Add[x, l.rest]]]; ENDCASE => ERROR; }; stream: STREAM = IO.RIS[rope]; list _ NIL; FOR rl: RopeList _ globals, rl.rest WHILE rl # NIL DO raw: ROPE = rl.first; wl: Wordlist = FindWordlist[file, raw, NIL, NIL]; list _ Add[wl, list]; ENDLOOP; FOR i: INT _ stream.SkipWhitespace[], stream.SkipWhitespace[] WHILE NOT stream.EndOf[] DO raw: ROPE = stream.GetTokenRope[SepByWhite].token; wl: Wordlist; full: ROPE; cp: FS.ComponentPositions; [full, cp] _ FS.ExpandName[name: raw, wDir: wDir]; wl _ FindWordlist[file, full.Substr[len: cp.ext.start+cp.ext.length], NIL, NIL]; list _ Add[wl, list]; ENDLOOP; IF includeDocLocalWordlist THEN list _ Add[docLocalWordlist, list]; stream.Close[]; }; RopeListEqual: PROC [l1, l2: RopeList] RETURNS [equal: BOOL] = { DO IF l1 = l2 THEN RETURN [TRUE]; IF l1 = NIL OR l2 = NIL THEN RETURN [FALSE]; IF NOT Rope.Equal[l1.first, l2.first, FALSE] THEN RETURN [FALSE]; l1 _ l1.rest; l2 _ l2.rest; ENDLOOP; }; WordlistListEqual: PROC [l1, l2: WordlistList] RETURNS [equal: BOOL] = { DO IF l1 = l2 THEN RETURN [TRUE]; IF l1 = NIL OR l2 = NIL THEN RETURN [FALSE]; IF l1.first # l2.first THEN RETURN [FALSE]; l1 _ l1.rest; l2 _ l2.rest; ENDLOOP; }; ComputeLocalData: INTERNAL PROC [wDir, localAuxilliaryFileWordlistNamesRope: ROPE, auxilliaryWordlists: WordlistList] RETURNS [localData: LocalData] = { success: BOOL _ TRUE; localData _ NEW [LocalDataRep _ [ BitTableFilename: BitTableFilename, globalAuxilliaryFileWordlistNames: globalAuxilliaryFileWordlistNames, localAuxilliaryFileWordlistNamesRope: localAuxilliaryFileWordlistNamesRope, wDir: wDir, auxilliaryWordlists: auxilliaryWordlists]]; [success, localData.bitTable] _ GetBitFile[localData.BitTableFilename]; IF NOT success THEN RETURN [NIL]; FOR wll: WordlistList _ localData.auxilliaryWordlists, wll.rest UNTIL wll = NIL DO wl: Wordlist = wll.first; IF wl.homeType = doc THEN { IF localData.toRoot # NIL THEN ERROR; IF wl.toRoot = NIL THEN ERROR; localData.toRoot _ wl.toRoot}; MergeWordFile[wl, localData.bitTable]; IF wl.localDatas = NIL AND wl.wordsAddedList=NIL THEN { wordlists _ CONS[wl, wordlists]; MaybeAddWordlistInserter[wl, toolExists]; }; wl.localDatas _ CONS[localData, wl.localDatas]; ENDLOOP; localDataCache _ CONS[localData, localDataCache]; }; FindWordlist: INTERNAL PROC [ht: WordlistHomeType, name, rootName: ROPE, root: TiogaOps.Ref] RETURNS [wl: Wordlist] = { FOR fdl: WordlistList _ wordlists, fdl.rest WHILE fdl # NIL DO IF fdl.first.name.Equal[name, FALSE] THEN RETURN [fdl.first]; ENDLOOP; IF ht = doc AND NOT name.Equal[rootName] THEN ERROR; wl _ NEW [WordlistRep _ [homeType: ht, name: name, toRoot: root]]; TRUSTED {wl.tnRoot _ LOOPHOLE[wl.toRoot]}; SELECT ht FROM file => wl.empty _ FALSE; doc => { ra: REF ANY _ TextEdit.GetProp[wl.tnRoot, localWordlistProperty]; wl.empty _ IF ra = NIL THEN TRUE ELSE WITH ra SELECT FROM x: ROPE => FALSE, x: REF TEXT => FALSE, ENDCASE => TRUE; }; ENDCASE => ERROR; SELECT wl.homeType FROM file => NULL; doc => { IF NOT localInsertMenud THEN { localInsertMenud _ TRUE; Menus.AppendMenuEntry[ menu: toolMenu, line: 0, entry: localInsertMenuEntry _ Menus.CreateEntry[ name: localListName, proc: InsertWordInLocalList] ]; IF toolExists THEN ViewerOps.PaintViewer[container, menu]; }; }; ENDCASE => ERROR; }; MaybeAddWordlistInserter: INTERNAL PROC [wl: Wordlist, paint: BOOL] = { SELECT wl.homeType FROM file => { fullFName, entryName, ext: ROPE; cp: FS.ComponentPositions; IF wl.menuEntry # NIL THEN ERROR; [fullFName, cp, ] _ FS.ExpandName[wl.name]; IF cp.server.length = 0 THEN { entryName _ fullFName.Substr[start: cp.base.start, len: cp.base.length]; ext _ fullFName.Substr[start: cp.ext.start, len: cp.ext.length]; IF NOT ext.Equal["Wordlist", FALSE] THEN entryName _ entryName.Cat[".", ext]; Menus.AppendMenuEntry[ menu: toolMenu, line: 0, entry: wl.menuEntry _ Menus.CreateEntry[ name: entryName, proc: InsertWord, clientData: wl] ]; IF paint THEN ViewerOps.PaintViewer[container, menu]; }; }; doc => NULL; ENDCASE => ERROR; }; SepByWhite: PROC [char: CHAR] RETURNS [IO.CharClass] --IO.BreakProc-- = { RETURN [SELECT char FROM IN [0C .. ' ] => sepr, ENDCASE => other]; }; generalDestroyRegistration: ViewerEvents.EventRegistration _ ViewerEvents.RegisterEventProc[proc: NoteDestruction, event: destroy]; NoteDestruction: ENTRY PROC [viewer: Viewer, event: ViewerEvents.ViewerEvent, before: BOOL] RETURNS [abort: BOOL _ FALSE] --ViewerEvents.EventProc-- = { ENABLE UNWIND => NULL; localData: LocalData _ NARROW[ViewerOps.FetchProp[viewer, viewerLocalDataKey]]; IF event = destroy AND localData # NIL THEN { ViewerOps.AddProp[viewer, viewerLocalDataKey, NIL]; NoteNonUse[localData, viewer]; }; }; NoteNonUse: INTERNAL PROC [localData: LocalData, user: Viewer] = { localData.users _ FilterViewerList[user, localData.users]; IF localData.users = NIL THEN { myWordlists: WordlistList _ NIL; FOR myWordlists _ localData.auxilliaryWordlists, myWordlists.rest WHILE myWordlists # NIL DO wl: Wordlist _ myWordlists.first; wl.localDatas _ FilterLocalDataList[localData, wl.localDatas]; IF wl.localDatas = NIL THEN { IF wl.menuEntry # NIL THEN Unenter[wl, TRUE]; IF wl.wordsAddedList = NIL THEN wordlists _ FilterWordlistList[wl, wordlists]; }; ENDLOOP; localDataCache _ FilterLocalDataList[localData, localDataCache]; }; }; Unenter: PROC [wl: Wordlist, paint: BOOL] = { Menus.ReplaceMenuEntry[menu: toolMenu, oldEntry: wl.menuEntry, newEntry: NIL]; wl.menuEntry _ NIL; IF paint AND toolExists THEN ViewerOps.PaintViewer[container, menu]; }; FlushCache: INTERNAL PROC = { WHILE localDataCache # NIL DO user: Viewer _ localDataCache.first.users.first; ViewerOps.AddProp[user, viewerLocalDataKey, NIL]; NoteNonUse[localDataCache.first, user]; ENDLOOP; }; FilterViewerList: PROC [member: Viewer, oldList: ViewerList] RETURNS [newList: ViewerList] = { last: ViewerList _ NIL; newList _ oldList; FOR this: ViewerList _ newList, this.rest WHILE this # NIL DO IF this.first = member THEN { IF last = NIL THEN newList _ this.rest ELSE last.rest _ this.rest; RETURN; }; last _ this; ENDLOOP; ERROR; }; FilterLocalDataList: PROC [member: LocalData, oldList: LocalDataList] RETURNS [newList: LocalDataList] = { last: LocalDataList _ NIL; newList _ oldList; FOR this: LocalDataList _ newList, this.rest WHILE this # NIL DO IF this.first = member THEN { IF last = NIL THEN newList _ this.rest ELSE last.rest _ this.rest; RETURN; }; last _ this; ENDLOOP; ERROR; }; FilterWordlistList: PROC [member: Wordlist, oldList: WordlistList] RETURNS [newList: WordlistList] = { last: WordlistList _ NIL; newList _ oldList; FOR this: WordlistList _ newList, this.rest WHILE this # NIL DO IF this.first = member THEN { IF last = NIL THEN newList _ this.rest ELSE last.rest _ this.rest; RETURN; }; last _ this; ENDLOOP; ERROR; }; GetBitFile: INTERNAL PROC [fileName: ROPE] RETURNS [ succeeded: BOOLEAN _ FALSE, bitTable: BitTableLookup.Table] = { s: STREAM; bitTable _ NIL; IO.PutF[permMessage, "Reading word list from \"%g\"...", IO.rope[fileName]]; s _ FS.StreamOpen[fileName ! FS.Error => { permMessage.PutRope[" file could not be read.\n"]; GOTO openFailed}]; bitTable _ BitTableLookup.Read[s ! BitTableLookup.Failed => { permMessage.PutRope[SELECT why FROM TooLarge => " bit table too large.\n", BadTable => " contained bad table.\n", ENDCASE => ERROR]; GOTO readFailed}]; s.Close[]; succeeded _ TRUE; IO.Put[permMessage, IO.rope[" done.\n"]]; EXITS openFailed => NULL; readFailed => NULL; }; AddWord: INTERNAL PROC [wl: Wordlist, wordAsText: REF TEXT, wordAsRope: ROPE] = { totalInsertionsCnt _ totalInsertionsCnt + 1; IO.PutF[permMessage, "\"%g\" inserted in %g.\n", IO.rope[wordAsRope], IO.rope[SELECT wl.homeType FROM doc => "local document wordlist", file => wl.name, ENDCASE => ERROR]]; IF wl.localDatas = NIL AND wl.wordsAddedList=NIL THEN { wordlists _ CONS[wl, wordlists]; MaybeAddWordlistInserter[wl, toolExists]; }; wl.wordsAddedList _ CONS[wordAsRope, wl.wordsAddedList]; dirty _ TRUE; wl.empty _ FALSE; FOR ldl: LocalDataList _ wl.localDatas, ldl.rest WHILE ldl # NIL DO ldl.first.bitTable.Insert[wordAsText]; ENDLOOP; }; 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 [wl: Wordlist, bitTable: BitTableLookup.Table] = { wordFile: STREAM; SELECT wl.homeType FROM file => { IO.PutF[permMessage, "Adding words from file \"%g\"...", IO.rope[wl.name]]; wordFile _ FS.StreamOpen[wl.name ! FS.Error => GOTO openFailed]; }; doc => { ra: REF ANY _ TextEdit.GetProp[wl.tnRoot, localWordlistProperty]; WITH ra SELECT FROM x: ROPE => wordFile _ IO.RIS[x]; x: REF TEXT => wordFile _ IO.TIS[x]; ENDCASE => GOTO BadProp; }; ENDCASE => ERROR; DO word: REF TEXT _ NEW[TEXT[30]]; word _ GetToken[wordFile, word ! IO.EndOfStream => EXIT]; bitTable.Insert[word]; ENDLOOP; IO.Close[wordFile]; 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 insert words into it.\n"]]; BadProp => IO.Put[permMessage, IO.rope[" root's Wordlist property didn't have a wordlist value.\nOne will be given automatically if you insert words into it.\n"]]; }; SaveInsertions: INTERNAL PROC [] = { dirty _ FALSE; FOR fdl: WordlistList _ wordlists, fdl.rest WHILE fdl # NIL DO wl: Wordlist _ fdl.first; NILList: PROC = { wl.wordsAddedList _ NIL; IF wl.localDatas = NIL THEN wordlists _ FilterWordlistList[wl, wordlists]; }; IF wl.wordsAddedList # NIL THEN { wordFile: STREAM _ NIL; lock: BOOL; sep: CHAR; root: TextNode.Ref = wl.tnRoot; SELECT wl.homeType FROM file => {sep _ '\n; wordFile _ FS.StreamOpen[wl.name, $append ! FS.Error => {lock _ error.group = lock; CONTINUE}]; }; doc => {sep _ ' ; wordFile _ IO.ROS[]; WITH TextEdit.GetProp[root, localWordlistProperty] SELECT FROM x: ROPE => wordFile.PutRope[x]; x: REF TEXT => wordFile.PutText[x]; ENDCASE => NULL; }; ENDCASE => ERROR; IF wordFile # NIL THEN { FOR w: RopeList _ wl.wordsAddedList, w.rest UNTIL w = NIL DO wordFile.Put[IO.rope[w.first]]; wordFile.PutChar[sep]; ENDLOOP; SELECT wl.homeType FROM file => wordFile.Close[]; doc => TextEdit.PutProp[root, localWordlistProperty, wordFile.RopeFromROS[], NIL, root]; ENDCASE => ERROR; NILList[]; } ELSE { IO.PutF[permMessage, "The insertion save file \"%g\" could not be written", IO.rope[wl.name]]; IF lock THEN --try again later-- { IO.PutRope[permMessage, " due to lock conflict --- will try again later.\n"]; dirty _ TRUE; } ELSE { IO.PutRope[permMessage, "; insertions lost.\n"]; NILList[]; }; }; }; ENDLOOP; DoStats[]; }; DoStats: INTERNAL PROC = { [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, reportStatisticsTo, "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; CleanlinessWanted: CONDITION; CleanlinessAchieved: CONDITION; MakeAdditionsPermanent: ENTRY PROC = TRUSTED { ENABLE UNWIND => NULL; DO IF insertionInterval > 0 THEN Process.SetTimeout[@CleanlinessWanted, Process.SecondsToTicks[insertionInterval]] ELSE Process.DisableTimeout[@CleanlinessWanted]; SaveInsertions[]; BROADCAST CleanlinessAchieved; IF NOT toolExists THEN EXIT; WAIT CleanlinessWanted; 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; wl: Wordlist _ NARROW[clientData]; --IF GotBitTable[] THEN-- { InsertWordOrInsertWordAndSpell[wl, mouseButton # red, TRUE, TRUE]; IF mouseButton # red THEN totalCheckCommands _ totalCheckCommands + 1; }; }; InsertWordInLocalList: ENTRY Menus.MenuProc = { ENABLE UNWIND => NULL; localName: ROPE = LocalListName[lastRoot]; wl: Wordlist = FindWordlist[doc, localName, localName, lastRoot]; --IF GotBitTable[] THEN-- { InsertWordOrInsertWordAndSpell[wl, mouseButton # red, TRUE, TRUE]; IF mouseButton # red THEN totalCheckCommands _ totalCheckCommands + 1; }; }; InsertHead: ENTRY Menus.MenuProc = { ENABLE UNWIND => NULL; permMessage.PutRope["Click on name of wordlist you wish to insert in.\n"]; }; 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"]; }; theWordCorrected: ROPE _ NIL; lastWordCorrected: ROPE _ NIL; correctionBuffer: REF TEXT _ NEW[TEXT[20]]; ProposeCorrections: INTERNAL PROC [localData: LocalData, theWord: ROPE] = { WordChecker: INTERNAL PROC [w: REF TEXT] RETURNS [inTable: BOOL] = { inTable _ localData.bitTable.Lookup[w]; }; 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: RopeList _ 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: RopeList _ 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 - ViewerPrivate.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: ApplyCorrectionButton ] ]; widthLeft _ widthLeft - width - ViewerPrivate.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 PROC [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift, control: BOOL _ FALSE] --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; }; ResetButton: ENTRY Menus.MenuProc = { ENABLE UNWIND => NULL; permMessage.PutRope["Freeing bit tables ... "]; FlushCache[]; permMessage.PutRope[" ... ensuring insertions written ... "]; WaitTillClean[]; permMessage.PutRope[" ... done resetting.\n"]; }; 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: Viewer _ NIL; registration: ViewerEvents.EventRegistration; QuitSpellTool: ENTRY ViewerEvents.EventProc = { ENABLE UNWIND => NULL; IF viewer = container AND event = destroy THEN { SpellingToolDefinition.FinalizeDictionaryServer[]; SaveInsertions[]; toolExists _ FALSE; container _ NIL; ViewerEvents.UnRegisterEventProc[registration, destroy]; }; }; DefineMenu: PROC = { Menus.AppendMenuEntry [line: 0, menu: toolMenu, entry: Menus.CreateEntry[name: "Reset", proc: ResetButton, guarded: TRUE]]; Menus.AppendMenuEntry [line: 0, menu: toolMenu, entry: Menus.CreateEntry[name: "Definition", proc: SpellingToolDefinition.DefinitionButton]]; Menus.AppendMenuEntry [line: 0, menu: toolMenu, entry: Menus.CreateEntry[name: "Check", proc: CheckSpelling]]; Menus.AppendMenuEntry [line: 0, menu: toolMenu, entry: Menus.CreateEntry[name: "Insert in:", proc: InsertHead]]; }; NewSpellingTool: ENTRY PROC RETURNS [new: BOOL] = { ENABLE UNWIND => NULL; ymax: INTEGER _ 0; emWidth: INTEGER _ VFonts.CharWidth['M]; emHeight: INTEGER _ VFonts.FontHeight[]; ts: TypeScript.TS; IF toolExists THEN RETURN [FALSE]; toolExists _ new _ TRUE; container _ Containers.Create[[name: "Spelling Tool", iconic: TRUE, column: right, menu: toolMenu, scrollable: FALSE, icon: spellingToolIcon]]; 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]; }; WaitTillClean: INTERNAL PROC = { WHILE dirty DO BROADCAST CleanlinessWanted; WAIT CleanlinessAchieved; ENDLOOP; }; Remove: PROC [in, old: ROPE] RETURNS [removed: ROPE, changed: BOOL] = { start: INT; removed _ in; changed _ FALSE; WHILE (start _ removed.Find[old]) >= 0 DO removed _ removed.Replace[start, old.Length[], NIL]; changed _ TRUE; ENDLOOP; }; InitializeProfile: ENTRY UserProfile.ProfileChangedProc = { ENABLE UNWIND => NULL; oldBitTableFilename: ROPE _ BitTableFilename; oldGlobalAuxilliaryWordlistNames: RopeList _ globalAuxilliaryWordlistNames; tail: RopeList _ globalAuxilliaryFileWordlistNames _ NIL; globalAuxilliaryWordlistNames _ UserProfile.ListOfTokens["SpellingTool.Wordlists", LIST["SpellingTool.Wordlist", "(local)"]]; localInEvery _ FALSE; FOR rl: RopeList _ globalAuxilliaryWordlistNames, rl.rest WHILE rl # NIL DO IF rl.first.Equal[localListName, FALSE] THEN localInEvery _ TRUE ELSE { this: RopeList = LIST[FS.ExpandName[name: rl.first, wDir: installationDirectory].fullFName]; IF tail = NIL THEN globalAuxilliaryFileWordlistNames _ this ELSE tail.rest _ this; tail _ this; }; ENDLOOP; BitTableFilename _ FS.ExpandName[ name: UserProfile.Token[ key: "SpellingTool.BitTable", default: "SpellingTool.BitTable"], wDir: installationDirectory ].fullFName; insertionInterval _ UserProfile.Number["SpellingTool.InsertionInterval", 60]; pluralStripping _ UserProfile.Boolean["SpellingTool.PluralStripping", TRUE]; IF NOT (oldBitTableFilename.Equal[BitTableFilename, FALSE] AND RopeListEqual[ oldGlobalAuxilliaryWordlistNames, globalAuxilliaryWordlistNames]) THEN FlushCache[]; BROADCAST CleanlinessWanted; }; SpellingToolCommand: Commander.CommandProc = { IF NewSpellingTool[] THEN { UserProfile.CallWhenProfileChanges[InitializeProfile]; TRUSTED {Process.Detach[FORK MakeAdditionsPermanent[]];}; }; }; TRUSTED {Process.SetTimeout[@CleanlinessAchieved, Process.SecondsToTicks[30]]}; DefineMenu[]; Commander.Register[ key: "SpellingTool", proc: SpellingToolCommand, doc: "Makes a Spelling Tool."]; END. CHANGE LOG Created by Nix on February 25, 1985 5:38:53 pm PST, END ˜SpellingToolImpl.mesa Copyright c 1985 by Xerox Corporation. All rights reserved. Last Edited by: Nix, December 2, 1983 11:57 am Mike Spreitzer July 28, 1986 2:28:08 pm PDT Rick Beach, May 2, 1985 3:30:17 pm PDT |-For interpreting localAuxilliaryFileWordlistNamesRope. |-Present iff auxilliaryWordlists includes a doc-local wordlist. |-Contains duplicate-filtered union of local and globals, sorted by name |-file => filename, |-doc => address of root |-For doc wordlists, this gives the doc; for file ones it's meaningless. |-Includes a ld iff ld.users # NIL & ld.auxilliaryWordlists includes me |-# NIL iff localDatas#NIL & homeType=file & file is not remote the property of document's root node that gives wordlist file names the property of document's root node that gives the local wordlist this names the wordlist local to a document. the property of a viewer that gives local data the icon The cache of files->bitTable |-Includes a ld iff ld.users # NIL All the wordlists in the world |-Includes a wl iff wl.localDatas#NIL or wl.wordsAddedList#NIL |-TRUE when some Wordlist have non-empty wordsAddedList. A background process will write these additions to the file, NIL-out the wordsAddedList, and reset the dirty bit. Handle for the word list. globalBitTable: BitTableLookup.Table _ NIL; Must now be local to document. We keep track of the root of the document most recently operated on, so that the local insert button knows which document to insert into. |-Includes an insert button for wl iff wl.localDatas#NIL & wl.homeType=file & file is not remote |-the insert button for operating on the local list of the last viewer opd in. The place for writing permanent messages, e.g. definitions. Parameters settable in User.Profile. List of the word lists that the user would like merged in to the global word list. |-TRUE iff localListName in globalAuxilliaryWordlistNames. 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. Get the right bit table & stuff. 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. 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"]; }; Saves the words the have been recently inserted into word lists in their files. scope of GVSend.SendFailed end scope Spreitzer, February 25, 1985 5:38:07 pm PST Added VM operations to accommodate Beach's addition of explicit VM management. changes to: DIRECTORY, SpellingToolImpl, SpellCheckWithin Spreitzer, February 27, 1985 11:42:53 am PST Introduced auxilliary wordlist file lists local to document. changes to: , DIRECTORY Edited on February 27, 1985 8:46:37 pm PST, by Spreitzer This entry from NewStuff, rather than EditorComforts changes to: viewerLocalDataKey, LocalDataRep, FileDataRep, localDataCache, fileDatas, dirty, toolMenu, SpellCheckWithin, WordCollector (local of Locker, local of InsertWordOrInsertWordAndSpell), Locker (local of InsertWordOrInsertWordAndSpell), InsertWordOrInsertWordAndSpell, Valid, ValidCache (local of GetLocalData), UseIt (local of GetLocalData), ComputeLocalData, NoteNonUse, Unenter, FlushCache, FilterLocalDataList, GetBitFile, AddWord, SaveInsertions, NILList (local of SaveInsertions), ApplyInsertionButton, NewSpellingTool, FlushCacheButton, NoCorrectionButton, QuitSpellTool, DIRECTORY, STREAM, Viewer, ViewerList, FindFileData, FilterViewerList, ProposeCorrections, container, generalDestroyRegistration, NoteDestruction, Commander, GetLocalData, MaybeAddWordlistInserter, NewSpellingTool, SpellCheckWithin, DIRECTORY, toolMenu, NewSpellingTool, NewSpellingTool, TRUSTED, DefineMenu, FlushCache, ResetButton, InitializeProfile, SpellingToolImpl, localAuxilliaryWordlistFilenamesProperty, ProposeCorrections, Locker (local of ApplyCorrectionButton), ApplyCorrectionButton Edited on March 2, 1985 1:44:16 pm PST, by Spreitzer Changed default source of bit table from [Indigo]SpellingTool.BitTable to SpellingTool.BitTable changes to: InitializeProfile Spreitzer, February 26, 1986 0:27:31 am PST Added a wordlist local to document. changes to: , =, InsertWordOrInsertWordAndSpell, ComputeLocalData, FindWordlist, MaybeAddWordlistInserter, NoteNonUse, Unenter, FilterWordlistList, AddWord, SaveInsertions, NILList (local of SaveInsertions), InsertWord, WordCollector (local of Locker, local of InsertWordOrInsertWordAndSpell), WordlistHomeType, GetLocalData, WholeList (local of GetLocalData), ValidCache (local of GetLocalData), ParseToList, Add (local of ParseToList), WordlistListEqual, MergeWordFile, LocalListName, HomeTypeFromName, TextNodeRoot, Replace, InitializeProfile, DIRECTORY, SpellCheckWithin, InsertWord, DefineMenu Κ.Y˜code– "Cedar" stylešœ™K– "Cedar" stylešœ Οmœ1™™>—šœžœžœ˜Kšœͺ™ͺ—˜Kš ™—šœ(žœ™,K™—˜K™‰—Kšœžœ˜K˜Kšœ žœžœ˜šœ2˜2Kšœ`™`—K˜Kšœžœžœ˜šœ(žœ˜,KšœN™N—˜Kšœ;™;—Kšœ žœžœ˜Kšœžœ˜K˜Kšœžœžœ˜Kšœžœ ˜$Kšœ žœ˜Kšœžœ˜—Kšœ„žœ˜ŒK˜šœ$™$K™KšœR™R—Kšœ(˜(Kšœ,˜,šœžœ˜Kšœ:™:—˜Kš &™&—Kšœžœ˜˜Kš B™B—Kšœžœ˜˜Kš =™=—Kšœžœžœ˜K˜K˜K˜K˜šΟnœžœžœ-žœ˜UKšœ€™€K˜*K˜%Kšœ žœ˜Kšœ Οsž’œ˜K˜'K˜š‘œžœžœžœžœžœžœ˜RKšœ.˜.šžœ ž˜K˜%—K˜ š žœžœžœžœ,žœž˜fK˜—K˜K˜—š‘œžœžœžœžœžœžœžœ˜WKšœžœ˜Kšœžœ˜ Kšžœžœžœ˜ šžœžœ˜Kšœ žœ˜Kšžœ˜K˜—Kšžœžœžœ˜K˜K˜šžœ žœ˜Kšžœ žœžœ˜šžœ žœ˜K˜Kšœ.˜.K˜K˜—šžœ˜K˜Kšœ.˜.K˜K˜—K˜—K˜—šΠbnœžœžœ˜2Kšœ:žœžœ ˜OK˜K˜—šžœžœžœ˜K˜&Kšžœ˜—Kšœ'˜'Kšžœ-˜/šžœžœ˜Kšœ/˜/Kšœ6˜6K˜—šžœžœ˜šžœ žœ˜K˜>K˜6K˜—šžœ˜K˜KK˜K˜—K˜—K˜fšžœ žœ žœ˜$šžœžœ˜Kšœ-˜-Kšœ6˜6K˜—šžœ˜Kšœ˜Kšœ˜K˜—K˜IK˜K˜fK˜—K˜;šžœ žœ˜KšœI˜IKšœ6˜6KšœFžœžœ˜_K˜$K˜K˜—šžœ˜šžœ,žœ˜4Kšœ-˜-Kšœ,˜,Kšœ'˜'K˜—šžœžœ˜KšžœA˜CK˜$K˜—šžœžœžœ˜Kšžœ?˜A—šžœ˜Kšžœ;žœ˜SK˜$K˜—K˜—Kšžœ+˜-šž˜Kšœžœ˜—K˜K˜—K˜š‘œžœžœ0žœ˜fKšœ›™›K˜K˜ K˜Kšœ žœ˜K˜*Kšœžœžœ˜'K˜š£œžœžœ˜2š‘ œžœžœžœžœžœžœžœ˜NKšœ*˜*K˜K˜—Kšœžœžœ˜/K˜šžœ˜Kšžœ`˜dKšžœ'˜+—K˜—Kšœ>žœ˜PKšžœžœžœžœ˜$šžœž˜K˜—šžœ žœ˜šžœ žœ˜K˜K˜ K˜—šžœ˜K˜K˜ K˜—Kšœžœ˜0K˜—šž˜Kšœžœ*˜;—K˜K˜K˜—š‘ œžœžœžœ˜AKšœžœžœ ˜2K˜K˜—š‘œžœžœžœ˜FKšœžœžœžœ˜-K˜—š ‘œžœžœžœ žœ˜EKšœžœ˜K˜K˜—š‘ œžœžœžœ˜MK™ Kšœ1˜1Kšœ žœ˜%Kšœ&žœ˜+Kšœ$žœ˜(K˜KšœBžœžœ˜OKšœžœ˜š ‘ œžœžœžœžœ˜?šžœžœžœ˜Kšœœ˜œKšœžœ˜—Kšœ˜—Kšœžœ1˜;š ‘ œžœžœžœ žœ˜3šœ˜Kšœ9žœ˜?šž˜šœ˜šœ ˜ Kšœ/˜/Kšœ%˜%Kšžœ˜—Kšžœ"žœ˜+šžœ˜Kšœ,˜,Kšœ"˜"—Kšžœ˜—Kšžœ@˜B——K˜—Kšœ&žœ4˜`š‘œžœžœžœ˜0Kšœ@˜@Kšžœžœžœ$˜>Kšžœ žœžœžœ˜JKšœ˜—KšœKžœO˜ Kšœ5žœžœžœH˜•Kšžœžœ˜/šžœ žœžœžœžœžœžœ˜Išžœ/žœžœž˜DKšœ˜Kšžœžœžœžœ ˜;Kšžœ˜—K˜K˜Kšœ^˜^K˜ Kšœ˜—K˜K˜—š ‘ œžœžœžœ.žœžœ˜œš‘œžœ žœ˜HKš žœžœžœžœžœ˜!šžœ$žœž˜5Kšœžœžœ˜Kšœ žœ˜Kšœ žœžœ˜2Kšžœžœ˜—K˜—Kšœžœžœžœ˜Kšœžœ˜ šžœ!žœžœž˜5Kšœžœ ˜Kšœ'žœžœ˜1K˜Kšžœ˜—š žœžœ4žœžœž˜YKšœžœ)˜2Kšœ ˜ Kšœžœ˜ Kšœžœ˜Kšœ žœ#˜2KšœFžœžœ˜PK˜Kšžœ˜—Kšžœžœ$˜CK˜K˜K˜—š‘ œžœžœ žœ˜@šž˜Kšžœ žœžœžœ˜Kšžœžœžœžœžœžœžœ˜,Kš žœžœ žœžœžœžœ˜AKšœ ˜ Kšœ ˜ Kšžœ˜—K˜K˜—š‘œžœžœ žœ˜Hšž˜Kšžœ žœžœžœ˜Kšžœžœžœžœžœžœžœ˜,Kšžœžœžœžœ˜+Kšœ ˜ Kšœ ˜ Kšžœ˜—K˜K˜—š ‘œžœžœ.žœ%žœ˜˜Kšœ žœžœ˜šœ žœ˜!Kšœ#˜#KšœE˜EKšœK˜KK˜ Kšœ+˜+—KšœG˜GKš žœžœ žœžœžœ˜!šžœ=žœžœž˜RK˜šžœžœ˜Kšžœžœžœžœ˜%Kšžœ žœžœžœ˜Kšœ˜—Kšœ&˜&š žœžœžœžœžœ˜7Kšœ žœ˜ Kšœ)˜)Kšœ˜—Kšœžœ˜/Kšžœ˜—Kšœžœ˜1K˜K˜—š ‘ œžœžœ(žœžœ˜wšžœ)žœžœž˜>Kšžœžœžœžœ ˜=Kšžœ˜—Kš žœ žœžœžœžœ˜4Kšœžœ:˜BKšžœžœ ˜*šžœž˜Kšœžœ˜˜Kšœžœžœ6˜Ašœ žœžœžœžœžœžœžœž˜9Kšœžœžœ˜Kšœžœžœžœ˜Kšžœžœ˜—K˜—Kšžœžœ˜—šžœ ž˜Kšœžœ˜ ˜šžœžœžœ˜Kšœžœ˜˜K˜K˜˜0Kšœ˜K˜—K˜—Kšžœ žœ(˜:K˜—K˜—Kšžœžœ˜—K˜K˜—š‘œžœžœžœ˜Gšžœ ž˜˜ Kšœžœ˜ Kšœžœ˜Kšžœžœžœžœ˜!Kšœžœ˜+šžœžœ˜KšœH˜HKšœ@˜@Kšžœžœžœžœ%˜M˜K˜K˜˜(Kšœ˜K˜K˜—K˜—Kšžœžœ(˜5K˜—K˜—Kšœžœ˜ Kšžœžœ˜—K˜K˜—š ‘ œžœžœžœžœ  œ˜Išžœžœž˜Kšžœ˜Kšžœ ˜—K˜K˜—Kšœƒ˜ƒK˜š£œžœžœ;žœžœ žœžœ œ˜˜Kšžœžœžœ˜Kšœžœ2˜Ošžœžœ žœžœ˜-Kšœ.žœ˜3Kšœ˜K˜—K˜K˜—š‘ œžœžœ)˜BK˜:šžœžœžœ˜Kšœžœ˜ šžœ?žœžœž˜\K˜!K˜>šžœžœžœ˜Kšžœžœžœ žœ˜-Kšžœžœžœ/˜NK˜—Kšžœ˜—Kšœ@˜@K˜—K˜K˜—š‘œžœžœ˜-KšœIžœ˜NKšœžœ˜Kšžœžœ žœ(˜DK˜K˜—š‘ œžœžœ˜šžœžœž˜K˜0Kšœ,žœ˜1Kšœ'˜'Kšžœ˜—K˜K˜—š‘œžœ'žœ˜^Kšœžœ˜K˜šžœ'žœžœž˜=šžœžœ˜Kšžœžœžœžœ˜BKšžœ˜K˜—K˜ Kšžœ˜—Kšžœ˜K˜K˜—š‘œžœ-žœ˜jKšœžœ˜K˜šžœ*žœžœž˜@šžœžœ˜Kšžœžœžœžœ˜BKšžœ˜K˜—K˜ Kšžœ˜—Kšžœ˜K˜K˜—š‘œžœ+žœ˜fKšœžœ˜K˜šžœ)žœžœž˜?šžœžœ˜Kšžœžœžœžœ˜BKšžœ˜K˜—K˜ Kšžœ˜—Kšžœ˜K˜K˜—š‘ œžœžœ žœžœžœžœ%˜tKšœ=™=K˜Kšœžœ˜ Kšœ žœ˜Kšžœ7žœ˜Lšœžœ˜šžœ ˜ Kšœ2˜2Kšžœ˜——šœ"˜"šœ˜šœžœž˜#Kšœ&˜&Kšœ&˜&Kšžœžœ˜—Kšžœ˜——K˜ Kšœ žœ˜Kšžœžœ˜)šž˜Kšœžœ˜Kšœžœ˜—K˜K˜—š ‘œžœžœžœžœžœ˜QK˜-Kšžœ/žœžœžœ žœ4žœžœ˜¬š žœžœžœžœžœ˜7Kšœ žœ˜ Kšœ)˜)Kšœ˜—Kšœžœ ˜8Kšœžœ˜ Kšœ žœ˜šžœ.žœžœž˜CK˜&Kšžœ˜—K˜K˜—š‘œžœžœžœžœžœžœžœžœ˜MKšœH™Hš‘ œžœžœžœžœžœžœ˜KKšœ#™#Kšœžœ˜$K˜—Kšœžœ˜K˜ Kšžœžœžœ˜/K˜ K˜šžœžœ ž˜Kšœžœžœ˜.Kšžœžœžœ˜!šžœžœ˜Kšœžœžœ˜K˜K˜—K˜ šž˜K˜—Kšžœ˜—˜K˜—K˜—š‘ œžœžœ3˜OKšœQ™QKšœ žœ˜šžœ ž˜˜ Kšžœ7žœ˜KKšœ žœžœ žœ ˜@K˜—˜Kšœžœžœ6˜Ašžœžœž˜Kšœžœžœžœ˜ Kš œžœžœžœžœ˜$Kšžœžœ ˜—K˜—Kšžœžœ˜—šž˜Kš œžœžœžœžœ˜Kšœ!žœžœ˜9Kšœ˜Kšžœ˜—Kšžœ˜Kšžœžœ˜)šž˜˜ Kšžœžœg˜}—˜ Kšžœžœ‚˜˜——K˜K˜K˜—š ‘ œžœžœžœ žœ™K˜š‘œžœ˜Kšœžœ˜Kšžœžœžœ/˜JK˜—šžœžœžœ˜!Kšœ žœžœ˜Kšœžœ˜ Kšœžœ˜ Kšœ˜šžœ ž˜˜Kšœ žœžœ&žœ˜_K˜—˜Kšœ žœžœ˜šžœ/žœž˜>Kšœžœ˜Kšœžœžœ˜#Kšžœžœ˜—K˜—Kšžœžœ˜—šžœ žœžœ˜šžœ)žœžœž˜žœ˜PKšœ,˜,šžœžœžœ˜-Kšœ˜K˜ Kšœžœžœžœ˜&K˜—šžœ˜Kšœžœžœ˜.—K˜—šž˜Kšœžœ*˜;—K˜K˜—šΟb œžœ˜$Kšžœžœžœ˜Kšœžœ ˜"š œ˜Kšœ6žœžœ˜Bšžœžœ˜Kšœ,˜,—K˜—K˜K˜—š₯œžœ˜/Kšžœžœžœ˜Kšœ žœ˜*KšœA˜Aš œ˜Kšœ6žœžœ˜Bšžœžœ˜Kšœ,˜,—K˜—K˜K˜K˜—š₯ œžœ˜$Kšžœžœžœ˜KšœJ˜JK˜K˜K˜—š₯œžœ˜ Kšžœžœžœ˜Kšœ ˜ š£œžœžœ˜2Kšœžœžœžœ˜+Kšœ˜K˜ K˜(K˜—š œ˜Kšœ>žœ˜PKšœ,˜,Kšœžœžœžœ˜&K˜—šž˜Kšœžœ*˜;—K˜K˜K˜—Kšœžœžœ˜Kšœžœžœ˜Kš œžœžœžœžœ˜+K˜š£œžœžœ!žœ˜Kš‘ œžœžœžœžœžœ žœ˜DK˜'K˜—š ‘ œžœžœ žœžœžœ˜PKšžœžœžœžœ˜8K˜—Kšœžœ˜Kšœ žœ˜(Kšœžœ*˜?Kšœžœ˜Kšœžœ˜šžœ žœžœ˜KšžœA˜CKšžœ˜K˜—K˜.K˜K˜šœ%˜%KšœP˜P—Kšžœžœžœ#˜^šžœžœžœ˜Kšœžœ-˜BK˜—Kšœ ˜ Kšœ˜Kšœ-˜-šžœžœžœ˜K˜ Kšœ!žœ˜&Kšœ‘˜‘KšžœIžœ˜fK˜—šžœ˜šžœ&žœžœž˜9Kšœžœ˜)šžœžœžœ˜7šžœ žœžœ˜(KšžœT˜VKšžœ˜K˜—K˜Kšœ5˜5Kšœ'žœ˜,K˜—šœ˜Kšœ˜Kšœ˜šœ˜Kšœ˜Kšœ žœžœ žœ ˜žœ-žœ˜KšœF˜FKšœ9žœžœžœ˜hKšœkžœ˜ršžœ˜ KšœžœA˜TKšœ˜—K˜K˜3K˜&K˜&K˜K˜&K˜K˜—K˜š£ œžœžœ˜ šžœž˜Kšž œ˜Kšžœ˜Kšžœ˜—K˜K˜—š ‘œžœ žœžœ žœ žœ˜GKšœžœ˜ Kšœ ˜ Kšœ žœ˜šžœ"ž˜)Kšœ0žœ˜5Kšœ žœ˜Kšžœ˜—K˜K˜—š₯œžœ#˜;Kšžœžœžœ˜Kšœžœ˜-KšœK˜KKšœ5žœ˜9KšœSžœ&˜}Kšœžœ˜šžœ7žœžœž˜Kšžœžœ˜'Kšžœž˜šžœ˜KšœžœžœD˜\Kšžœžœžœ*žœ˜RK˜ Kšœ˜——Kšžœ˜—šœžœ ˜!˜K˜K˜"—K˜K˜ —K˜MKšœFžœ˜Lšž˜šž˜Kšœ-žœ˜3šžœ˜Kšœ!˜!Kšœ˜——Kšžœ˜—Kšž œ˜K˜K˜—š₯œ˜.šžœžœ˜K˜6Kšžœžœ˜9K˜—K˜K˜—KšžœH˜OK˜ šœ˜Kšœ˜Kšœ˜Kšœ˜——K˜šžœ˜Kšžœž˜ Kšœ/žΟrΠkr˜7—K˜™+K™NKšœ ¦-™9—™,K™