<> <> <> <> <> 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, <<|-For interpreting localAuxilliaryFileWordlistNamesRope.>> toRoot: TiogaOps.Ref _ NIL, <<|-Present iff auxilliaryWordlists includes a doc-local wordlist.>> auxilliaryWordlists: WordlistList _ NIL, <<|-Contains duplicate-filtered union of local and globals, sorted by name>> bitTable: BitTableLookup.Table _ NIL, users: ViewerList _ NIL ]; WordlistList: TYPE = LIST OF Wordlist; Wordlist: TYPE = REF WordlistRep; WordlistRep: TYPE = RECORD [ name: ROPE, <<|-file => filename,>> <<|-doc => address of root>> homeType: WordlistHomeType, toRoot: TiogaOps.Ref _ NIL, <<|-For doc wordlists, this gives the doc; for file ones it's meaningless.>> tnRoot: TextNode.Ref _ NIL, empty: BOOL _ TRUE, localDatas: LocalDataList _ NIL, <<|-Includes a ld iff ld.users # NIL & ld.auxilliaryWordlists includes me>> wordsAddedList: RopeList _ NIL, menuEntry: Menus.MenuEntry _ NIL <<|-# NIL iff localDatas#NIL & homeType=file & file is not remote>> ]; 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]; <bitTable>> localDataCache: LocalDataList _ NIL; <<|-Includes a ld iff ld.users # NIL>> <> wordlists: WordlistList _ NIL; <<|-Includes a wl iff wl.localDatas#NIL or wl.wordsAddedList#NIL>> dirty: BOOL _ FALSE; <<|-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.>> <> <> <> <> lastRoot: TiogaOps.Ref _ NIL; toolExists: BOOL _ FALSE; toolMenu: Menus.Menu _ Menus.CreateMenu[lines: 1]; <<|-Includes an insert button for wl iff wl.localDatas#NIL & wl.homeType=file & file is not remote>> localInsertMenud: BOOL _ FALSE; localInsertMenuEntry: Menus.MenuEntry _ NIL; <<|-the insert button for operating on the local list of the last viewer opd in.>> <> 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; <<|-TRUE iff localListName in globalAuxilliaryWordlistNames.>> <> 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 <> <> <> <> <> <> <<>> <> <> <> <<>> <<>> <<>> <<>> <<>> <<>> <<>> <<>> <<>> <<>> <<>> <<>> <<>> <<>> <> <SpellingTool.BitTable to SpellingTool.BitTable>> <> <> <> <> <<>> <<>> <<>> <<>> <<>> <<>> <<>>