DIRECTORY Atom USING [MakeAtom, DottedPairNode, DottedPair], BitTableLookup USING [Failed, Insert, Lookup, Read, Table], Commander USING [CommandProc, Register], CommanderOps USING [ArgumentVector, Failed, Parse], Containers USING [ChildXBound, ChildYBound, Create], Convert USING [AtomFromRope, Error, IntFromRope], Basics USING [LongNumber, Comparison, BITAND], PFS USING [AbsoluteName, Error, GetWDir, PATH, PathFromRope, RopeFromPath, StreamOpen], PFSNames USING [ComponentRope, ShortName], Icons USING [IconFlavor, NewIconFromFile], IO, List USING [UniqueSort], Menus USING [AppendMenuEntry, ChangeNumberOfLines, CreateEntry, CreateMenu, GetNumberOfLines, Menu, MenuEntry, MenuLine, MenuProc, MouseButton, ReplaceMenuEntry, SetLine], Process USING [Detach, DisableTimeout, SecondsToTicks, SetTimeout, Yield], RefText, Rope, SpellingCorrection USING [BruteForceCorrection], SpellingToolServices USING [InitializeDictionaryServer, FinalizeDictionaryServer, DefinitionButton, DefinitionStats, NerdType, Pronunciation, Synonyms, WordNerd], SpellingToolShared USING [CharSet, CheckMiddleOfWord, FirstWithin, MapWordsInSelection, Processed, ProcessSelection, Selection, ToRope], SystemNames USING [UserName], TEditOps USING [WorkingDirectoryFromViewer], TEditRefresh USING [ScrollToEndOfSel], TextEdit USING [GetCharProp, GetProp, PutCharProp, PutProp, RefTextNode], TextNode USING [LocOffset, Ref], TiogaOps USING [CallWithLocks, Delete, FetchLooks, GetProp, GetRope, InsertRope, LastWithin, NoSelection, Ref, Root, SaveForPaste, SelectionGrain, SetLooks, SetSelection, StepBackward], TiogaOpsDefs USING [Location, Ref], TypeScript USING [TS, Create], 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], ViewerPrivate USING [menuHLeading, menuHSpace]; SpellingToolImpl: CEDAR MONITOR IMPORTS Atom, Basics, BitTableLookup, Commander, CommanderOps, Containers, Convert, PFS, PFSNames, Icons, IO, List, Menus, Process, RefText, Rope, SpellingCorrection, SpellingToolServices, SpellingToolShared, SystemNames, TEditOps, TEditRefresh, TextEdit, TextNode, TiogaOps, TypeScript, UserProfile, VFonts, ViewerBLT, ViewerEvents, ViewerIO, ViewerOps = 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; CharSet: TYPE ~ SpellingToolShared.CharSet; 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: PFS.PATH = PFS.GetWDir[]; localAuxilliaryWordlistNamesProperty: ATOM = $Wordlists; localWordlistProperty: ATOM = $Wordlist; localListName: ROPE = "(local)"; viewerLocalDataKey: ATOM ¬ Atom.MakeAtom[IO.PutFR1["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: 2]; localInsertMenud: BOOL ¬ FALSE; localInsertMenuEntry: Menus.MenuEntry ¬ NIL; permMessage: PUBLIC STREAM; permMessageIn: STREAM; reportStatistics: BOOL ¬ FALSE; reportStatisticsTo: ROPE ¬ NIL; 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 ¬ FALSE; BitTableFilename: ROPE; insertionInterval: INT ¬ 60; pluralStripping: BOOL ¬ TRUE; minKeyWords: INT ¬ 1; minWords: INT ¬ 1; maxWords: INT ¬ 200; spellingToolNerd: SpellingToolServices.NerdType ¬ SpellingToolServices.NerdType.WordNerd; okProp: ATOM ~ $SpellingToolUserLikesThisWord; SpellCheckWithin: INTERNAL PROC [s: Selection, alphabetic: CharSet, 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, data: REF ANY, start: INT] RETURNS [misspelled: BOOL] = { misspelled ¬ ~localData.bitTable.Lookup[word]; IF misspelled THEN {node: TextEdit.RefTextNode ~ NARROW[data]; okr: ROPE ~ NARROW[TextEdit.GetCharProp[node, start, okProp]]; matches: BOOL; TRUSTED {matches ¬ okr.Equal[RefText.TrustTextAsRope[LOOPHOLE[word]]]}; IF matches THEN FOR i: NAT IN [1..word.length) DO IF NOT okr.Equal[NARROW[TextEdit.GetCharProp[node, start+i, okProp]]] THEN EXIT; REPEAT FINISHED => misspelled ¬ FALSE; ENDLOOP; }; IF misspelled THEN misspelled ¬ HandleMisspelling[word]; wordsChecked ¬ wordsChecked + 1; IF LOOPHOLE[Basics.BITAND[511, LOOPHOLE[wordsChecked, Basics.LongNumber].lo], 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]; TEditRefresh.ScrollToEndOfSel[s.viewer, FALSE, primary]; }; IF NOT Valid[localData] THEN { permMessage.PutRope["Can't check.\n"]; RETURN}; lastRoot ¬ TiogaOps.Root[s.start.node]; 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, alphabetic, 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, alphabetic, SpellingCheckerProc, forwards]; }; totalWordsCheckedCnt ¬ totalWordsCheckedCnt + wordsChecked; IF misspelled THEN { totalWordsProfitablyChecked ¬ totalWordsProfitablyChecked + wordsChecked; totalWordsMisspelledCnt ¬ totalWordsMisspelledCnt + 1; TiogaOps.CallWithLocks[Locker ! TiogaOps.NoSelection => {Locker[NIL]; GOTO noSelection}]; ProposeCorrections[localData, Rope.Substr[TiogaOps.GetRope[wordStart.node], wordStart.where, wordEnd.where-wordStart.where+1]]; } ELSE { IF Menus.GetNumberOfLines[container.menu] # 2 THEN { Menus.ChangeNumberOfLines[container.menu, 2]; ViewerBLT.ChangeNumberOfLines[container, 2]; ViewerOps.PaintViewer[container, menu]; }; IF wordsChecked = 0 THEN { IO.PutRope[permMessage, "The selection did not contain any words.\n"]; ViewerOps.BlinkIcon[s.viewer, 1, 1]; } ELSE IF wordsChecked = 1 THEN IO.PutRope[permMessage, "The selected word is correctly spelled.\n"] ELSE { IO.PutF1[permMessage, "No misspellings found in %g words.\n", IO.int[wordsChecked]]; ViewerOps.BlinkIcon[s.viewer, 1, 1]; }; }; EXITS noSelection => NULL; }; InsertWordOrInsertWordAndSpell: INTERNAL PROC [wl: Wordlist, andSpell, forwards, wrapAround: BOOL] = { s: Selection; alphabetic: CharSet; localData: LocalData; misspelled: BOOL; wordStart, wordEnd: TiogaOpsDefs.Location; auxilliaryWordlistFilename: ROPE ¬ NIL; Locker: INTERNAL PROC [root: REF ANY] = { rootNode: TextNode.Ref ~ NARROW[root]; WordCollector: INTERNAL PROC [word: REF TEXT, data: REF ANY, start: INT] RETURNS [stop: BOOL ¬ FALSE] = { wordAsRope: ROPE ~ Rope.FromRefText[word]; IF wl#NIL THEN AddWord[wl, word, wordAsRope] ELSE {node: TextEdit.RefTextNode ~ NARROW[data]; TextEdit.PutCharProp[node, start, okProp, wordAsRope, word.length]; IO.PutF[permMessage, "\"%g\" at %g marked OK.\n", IO.rope[wordAsRope], IO.int[TextNode.LocOffset[[rootNode, 0], [node, start]]]]}; RETURN}; [[s, alphabetic,]] ¬ ProcessSelection[FALSE, FALSE, forwards]; localData ¬ GetLocalData[s]; IF Valid[localData] THEN [misspelled, wordStart, wordEnd] ¬ MapWordsInSelection[s.start, s.end, alphabetic, WordCollector, forwards] ELSE permMessage.PutRope["Can't check.\n"]; }; TiogaOps.CallWithLocks[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, alphabetic, TRUE, forwards, wrapAround]; }; EXITS noSelection => IO.PutRope[permMessage, "Make a selection.\n"]; }; LocalListName: PROC [root: TiogaOps.Ref] RETURNS [name: ROPE] = { name ¬ IO.PutFR1["^%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; wDir: ROPE = TEditOps.WorkingDirectoryFromViewer[s.viewer]; oldLocalData: LocalData = localData ¬ NARROW[ViewerOps.FetchProp[s.viewer, viewerLocalDataKey]]; WholeList: INTERNAL PROC RETURNS [wll: WordlistList] = INLINE { IF NOT listComputed THEN { auxilliaryWordlists ¬ ParseToList[wDir, localAuxilliaryFileWordlistNamesRope, globalAuxilliaryFileWordlistNames, includeDocLocalWordlist, docLocalWordlist]; listComputed ¬ TRUE}; wll ¬ auxilliaryWordlists}; 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]); }; 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; full ¬ PFS.RopeFromPath[PFS.AbsoluteName[short: PFS.PathFromRope[raw], wDir: PFS.PathFromRope[wDir]]]; -- yuk! wl ¬ FindWordlist[file, full, 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 => { entryName, ext: ROPE; pos: INT; IF wl.menuEntry # NIL THEN ERROR; entryName ¬ PFSNames.ComponentRope[PFSNames.ShortName[PFS.PathFromRope[wl.name]]]; pos ¬ Rope.Find[entryName, "."]; IF pos > -1 THEN { ext ¬ entryName.Substr[pos+1]; IF ext.Equal["Wordlist", FALSE] THEN entryName ¬ entryName.Substr[0, pos]; }; 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 IO.SP, IO.CR, IO.TAB, IO.LF, IO.FF => 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.PutF1[permMessage, "Reading word list from \"%g\"...", IO.rope[fileName]]; s ¬ PFS.StreamOpen[PFS.PathFromRope[fileName] ! PFS.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.PutRope[permMessage, " 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 ¬ SELECT c FROM IO.SP, IO.CR, IO.TAB, IO.LF, IO.FF => TRUE, ENDCASE => FALSE; }; 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.PutF1[permMessage, "Adding words from file \"%g\"...", IO.rope[wl.name]]; wordFile ¬ PFS.StreamOpen[PFS.PathFromRope[wl.name] ! PFS.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.PutRope[permMessage, " done.\n"]; EXITS openFailed => IO.PutRope[permMessage, " file did not exist.\nThis file will be created automatically if you insert words into it.\n"]; BadProp => IO.PutRope[permMessage, " 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; sep: CHAR; root: TextNode.Ref = wl.tnRoot; SELECT wl.homeType FROM file => {sep ¬ '\n; wordFile ¬ PFS.StreamOpen[PFS.PathFromRope[wl.name], $append ! PFS.Error => 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.PutRope[w.first]; wordFile.PutChar[sep]; ENDLOOP; SELECT wl.homeType FROM file => wordFile.Close[]; doc => TextEdit.PutProp[root, localWordlistProperty, wordFile.RopeFromROS[], NIL]; ENDCASE => ERROR; NILList[]; } ELSE { IO.PutF1[permMessage, "The insertion save file \"%g\" could not be written; insertions lost.\n", IO.rope[wl.name]]; NILList[]; }; }; ENDLOOP; DoStats[]; }; DoStats: INTERNAL PROC = { [totalDefinitionsCnt, totalDefinitionsWorkedCount] ¬ SpellingToolServices.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, stats: Rope.ROPE; reportCount ¬ 0; name ¬ SystemNames.UserName[]; stats ¬ Rope.Cat[ IO.PutFLR["\nNumber of searches: %g\nNumber of words checked: %g\nNumber of words profitably checked: %g\nMisspellings found: %g\nInsertions made: %g\n", LIST[IO.int[totalCheckCommands], IO.int[totalWordsCheckedCnt], IO.int[totalWordsProfitablyChecked], IO.int[totalWordsMisspelledCnt], IO.int[totalInsertionsCnt]]], IO.PutFR1["Words corrected: %g\n", IO.int[totalCorrectionsCnt]], IO.PutFLR["Corrections proposed: %g\nCorrections accepted: %g\nDefinitions requested: %g\nDefinitions given: %g\nNotifications sent: %g\n", LIST[IO.int[totalCorrectionsProposedCnt], IO.int[totalCorrectionsApplied], IO.int[totalDefinitionsCnt], IO.int[totalDefinitionsWorkedCount], IO.int[totalMaintainerNotifications]]] ]; 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; }; }; 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; alphabetic: CharSet; wasExtended: BOOL ¬ FALSE; Locker: INTERNAL PROC [root: TiogaOpsDefs.Ref] = { [[s, alphabetic, wasExtended]] ¬ ProcessSelection[FALSE, TRUE, TRUE]; }; --IF GotBitTable[] THEN-- { TiogaOps.CallWithLocks[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, alphabetic, TRUE, TRUE, TRUE]; } ELSE SpellCheckWithin[s, alphabetic, FALSE, TRUE, wasExtended]; }; EXITS noSelection => IO.PutRope[permMessage, "Make a selection.\n"]; }; InsertWord: ENTRY Menus.MenuProc = { ENABLE UNWIND => NULL; wl: Wordlist ¬ NARROW[clientData]; IF wl=NIL THEN ERROR; --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 wl=NIL THEN ERROR; --IF GotBitTable[] THEN-- { InsertWordOrInsertWordAndSpell[wl, mouseButton # red, TRUE, TRUE]; IF mouseButton # red THEN totalCheckCommands ¬ totalCheckCommands + 1; }; }; MarkOK: ENTRY Menus.MenuProc = { ENABLE UNWIND => NULL; InsertWordOrInsertWordAndSpell[NIL, 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"]; }; Synonyms: PUBLIC ENTRY Menus.MenuProc = TRUSTED { ENABLE UNWIND => {}; theWord, text: Rope.ROPE; SynonymCommand: INTERNAL PROC [theWord: ROPE] = TRUSTED { text ¬ SpellingToolServices.Synonyms[theWord]; IO.PutRope[permMessage, text]; }; Locker: INTERNAL PROC [root: TiogaOpsDefs.Ref] = TRUSTED { ExtractOneWord: INTERNAL PROC [word: REF TEXT, data: REF ANY, start: INT] RETURNS [stop: BOOLEAN ¬ TRUE] = TRUSTED { theWord ¬ Rope.FromRefText[word]; }; p: Processed ~ ProcessSelection[FALSE,TRUE, TRUE]; s: Selection = p.s; ld: LocalData = GetLocalData[s]; [] ¬ SpellingToolShared.MapWordsInSelection[s.start, s.end, p.alphabetic, ExtractOneWord, TRUE]; IF theWord = NIL THEN IO.PutRope[permMessage, "No word has been selected.\n\n"] ELSE { IO.PutF1[permMessage, "\nLooking up synonyms for \"%g\"...", IO.rope[theWord]]; }; }; TiogaOps.CallWithLocks[Locker ! TiogaOps.NoSelection => { IO.PutRope[permMessage, "No word is selected; please select a word.\n"]; GOTO NoSynonym}]; IF theWord = NIL THEN { IO.PutRope[permMessage, "No word is selected; please select a word.\n"]; RETURN; }; [] ¬ SynonymCommand[theWord]; EXITS NoSynonym => NULL; }; Pronunciation: PUBLIC ENTRY Menus.MenuProc = TRUSTED { ENABLE UNWIND => {}; theWord, text: Rope.ROPE; PronunciationCommand: INTERNAL PROC [theWord: ROPE] = TRUSTED { text ¬ SpellingToolServices.Pronunciation[theWord]; IO.PutRope[permMessage, text]; }; Locker: INTERNAL PROC [root: TiogaOpsDefs.Ref] = TRUSTED { ExtractOneWord: INTERNAL PROC [word: REF TEXT, data: REF ANY, start: INT] RETURNS [stop: BOOLEAN ¬ TRUE] = TRUSTED { theWord ¬ Rope.FromRefText[word]; }; p: Processed = ProcessSelection[FALSE,TRUE, TRUE]; s: Selection = p.s; ld: LocalData = GetLocalData[s]; [] ¬ SpellingToolShared.MapWordsInSelection[s.start, s.end, p.alphabetic, ExtractOneWord, TRUE]; IF theWord = NIL THEN IO.PutRope[permMessage, "No word has been selected.\n\n"] ELSE { IO.PutF1[permMessage, "\nLooking up pronunciation for \"%g\"...", IO.rope[theWord]]; }; }; TiogaOps.CallWithLocks[Locker ! TiogaOps.NoSelection => { IO.PutRope[permMessage, "No word is selected; please select a word.\n"]; GOTO NoPronunciation}]; IF theWord = NIL THEN { IO.PutRope[permMessage, "No word is selected; please select a word.\n"]; RETURN; }; [] ¬ PronunciationCommand[theWord]; EXITS NoPronunciation => NULL; }; WordNerd: PUBLIC ENTRY Menus.MenuProc = TRUSTED { ENABLE UNWIND => {}; text: Rope.ROPE; theWords: Rope.ROPE; minKeyWordsThisTime: INT; CalculateMinKeyWords: PROC ~ TRUSTED { defaultMinKeyWords: INT ¬ minKeyWords; useDefaultMinKeyWords: BOOL ¬ FALSE; theWordsAsStream: STREAM ¬ IO.RIS[theWords]; minKeyWordsThisTime ¬ IO.GetInt[stream: theWordsAsStream ! IO.EndOfStream, IO.Error => {useDefaultMinKeyWords ¬ TRUE; CONTINUE}]; IF NOT useDefaultMinKeyWords THEN { [] ¬ IO.SkipWhitespace[stream: theWordsAsStream, flushComments: FALSE]; theWords ¬ Rope.Substr[theWords, IO.GetIndex[theWordsAsStream]]; }; IF useDefaultMinKeyWords THEN minKeyWordsThisTime ¬ defaultMinKeyWords; }; WordNerdCommand: INTERNAL PROC [theWords: ROPE] = TRUSTED { text ¬ SpellingToolServices.WordNerd[words: theWords, minKeyWords: minKeyWordsThisTime, minWords: minWords, maxWords: maxWords, nerd: spellingToolNerd]; IO.PutRope[permMessage, text]; }; Locker: INTERNAL PROC [root: TiogaOpsDefs.Ref] = TRUSTED { p: Processed = SpellingToolShared.ProcessSelection[FALSE,TRUE, TRUE]; theWords ¬ SpellingToolShared.ToRope[p.s]; CalculateMinKeyWords[]; IF Rope.Length[theWords] = 0 THEN IO.PutRope[permMessage, "No word has been selected.\n\n"] ELSE { IO.PutF[permMessage, "WordNerding \"%g\" (minKeyWords = %g) ...", IO.rope[theWords], [integer[minKeyWordsThisTime]]]; }; }; TiogaOps.CallWithLocks[Locker ! TiogaOps.NoSelection => { IO.PutRope[permMessage, "No word is selected; please select word(s).\n"]; GOTO NoNerd}]; IF theWords = NIL THEN { IO.PutRope[permMessage, "No word is selected; please select word(s).\n"]; RETURN; }; [] ¬ WordNerdCommand[theWords]; EXITS NoNerd => NULL; }; Ignore: ENTRY Menus.MenuProc = { ENABLE UNWIND => NULL; s: Selection; alphabetic: CharSet; Locker: INTERNAL PROC [root: TiogaOpsDefs.Ref] = { [[s, alphabetic, ]] ¬ ProcessSelection[FALSE, FALSE, TRUE]; s.start.node ¬ s.end.node; s.start.where ¬ s.end.where + 1; SpellingToolShared.CheckMiddleOfWord[s, alphabetic]; }; --IF GotBitTable[] THEN-- { TiogaOps.CallWithLocks[Locker ! TiogaOps.NoSelection => GOTO noSelection]; totalCheckCommands ¬ totalCheckCommands + 1; SpellCheckWithin[s, alphabetic, TRUE, TRUE, TRUE]; }; EXITS noSelection => IO.PutRope[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.PutRope[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.Concat[theWord, " --> "], correctionList]; }; lineNum ¬ 1; widthLeft ¬ 0; Menus.ChangeNumberOfLines[container.menu, 3]; IF correctionList = NIL THEN { lineNum ¬ 2; Menus.SetLine[container.menu, 2, NIL]; Menus.AppendMenuEntry[line: 2, menu: container.menu, entry: Menus.CreateEntry[name: "No corrections for this word.", proc: NoCorrectionButton]]; IO.PutF1[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.PutRope[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; alphabetic: CharSet; Locker: INTERNAL PROC [root: TiogaOpsDefs.Ref] = TRUSTED { ExtractOneWord: INTERNAL PROC [word: REF TEXT, data: REF ANY, start: INT] RETURNS [stop: BOOLEAN ¬ FALSE] = TRUSTED { stop ¬ wordCount > 0; IF wordCount = 0 THEN theWord ¬ Rope.FromRefText[word]; wordCount ¬ wordCount + 1; }; [[s, alphabetic,]] ¬ ProcessSelection[FALSE, TRUE, TRUE]; {ld: LocalData = GetLocalData[s]; [] ¬ MapWordsInSelection[s.start, s.end, alphabetic, ExtractOneWord, TRUE]; IF theWord = NIL THEN IO.PutRope[permMessage, "No word has been selected.\n"]; RETURN}}; 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]; }; TiogaOps.CallWithLocks[Locker ! TiogaOps.NoSelection => { IO.PutRope[permMessage, "No word is selected, so no correction can be applied.\n"]; GOTO leave;}]; IF wordCount = 0 THEN { IO.PutRope[permMessage, "No word is selected, so no correction can be applied.\n"]; RETURN; }; IF wordCount > 1 THEN { IO.PutRope[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 TiogaOps.CallWithLocks[MakeCorrection ! TiogaOps.NoSelection => { IO.PutRope[permMessage, "There is no selection, so no correction can be applied.\n"]; GOTO leave;}]; lastWordCorrected ¬ correction; IF mouseButton = blue THEN { [[s, alphabetic,]] ¬ ProcessSelection[FALSE,FALSE, TRUE]; s.start.node ¬ s.end.node; s.start.where ¬ s.end.where + 1; --IF GotBitTable[] THEN-- { totalCheckCommands ¬ totalCheckCommands + 1; SpellCheckWithin[s, alphabetic, 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.PutF1[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 { SpellingToolServices.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: "Check", proc: CheckSpelling]]; Menus.AppendMenuEntry [line: 0, menu: toolMenu, entry: Menus.CreateEntry[name: "MarkOK", proc: MarkOK]]; Menus.AppendMenuEntry [line: 0, menu: toolMenu, entry: Menus.CreateEntry[name: "Insert in:", proc: InsertHead]]; Menus.AppendMenuEntry [line: 1, menu: toolMenu, entry: Menus.CreateEntry[name: "Definition", proc: SpellingToolServices.DefinitionButton]]; Menus.AppendMenuEntry [line: 1, menu: toolMenu, entry: Menus.CreateEntry[name: "Pronunciation", proc: Pronunciation, guarded: FALSE]]; Menus.AppendMenuEntry [line: 1, menu: toolMenu, entry: Menus.CreateEntry[name: "Synonyms", proc: Synonyms]]; Menus.AppendMenuEntry [line: 1, menu: toolMenu, entry: Menus.CreateEntry[name: "WordNerd", proc: WordNerd, guarded: FALSE]]; }; 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: 3], paint: FALSE]; [permMessageIn, permMessage] ¬ ViewerIO.CreateViewerStreams[name: "Definitions", viewer: ts, editedStream: FALSE]; TRUSTED { Process.Detach[FORK SpellingToolServices.InitializeDictionaryServer[permMessage]]; }; 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["(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[PFS.RopeFromPath[PFS.AbsoluteName[short: PFS.PathFromRope[rl.first], wDir: installationDirectory]]]; IF tail = NIL THEN globalAuxilliaryFileWordlistNames ¬ this ELSE tail.rest ¬ this; tail ¬ this; }; ENDLOOP; BitTableFilename ¬ PFS.RopeFromPath[PFS.AbsoluteName[ short: PFS.PathFromRope[UserProfile.Token[ key: "SpellingTool.BitTable", default: "SpellingTool.BitTable"]], wDir: installationDirectory ]]; 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[]];}; }; }; WordNerdMinKeyWordsCommand: Commander.CommandProc = { argv: CommanderOps.ArgumentVector ¬ CommanderOps.Parse[cmd ! CommanderOps.Failed => {msg ¬ errorMsg; GO TO Die}]; SELECT argv.argc FROM 1 => { IO.PutF1[cmd.out, "WNMinKeyWords = %g\n", [integer[minKeyWords]]]; }; 2 => { minKeyWords ¬ Convert.IntFromRope[argv[1] ! Convert.Error => GO TO Usage]; }; ENDCASE => GO TO Usage; EXITS Die => result ¬ $Failure; Usage => RETURN[$Failure, "Usage: WNminKeyWords [minKeyWords]"]; }; WordNerdMinWordsCommand: Commander.CommandProc = { argv: CommanderOps.ArgumentVector ¬ CommanderOps.Parse[cmd ! CommanderOps.Failed => {msg ¬ errorMsg; GO TO Die}]; SELECT argv.argc FROM 1 => { IO.PutF1[cmd.out, "WNminWords = %g\n", [integer[minWords]]]; }; 2 => { minWords ¬ Convert.IntFromRope[argv[1] ! Convert.Error => GO TO Usage ]; }; ENDCASE => GO TO Usage; EXITS Die => result ¬ $Failure; Usage => RETURN[$Failure, "Usage: WNminWords [minWords]"]; }; WordNerdMaxWordsCommand: Commander.CommandProc = { argv: CommanderOps.ArgumentVector ¬ CommanderOps.Parse[cmd ! CommanderOps.Failed => {msg ¬ errorMsg; GO TO Die}]; SELECT argv.argc FROM 1 => { IO.PutF1[cmd.out, "WNmaxWords = %g\n", [integer[maxWords]]]; }; 2 => { maxWords ¬ Convert.IntFromRope[argv[1] ! Convert.Error => GO TO Usage]; }; ENDCASE => GO TO Usage; EXITS Die => result ¬ $Failure; Usage => RETURN[$Failure, "Usage: WNmaxWords [maxWords]"]; }; WordNerdReference: Commander.CommandProc = { argv: CommanderOps.ArgumentVector ¬ CommanderOps.Parse[cmd ! CommanderOps.Failed => {msg ¬ errorMsg; GO TO Die}]; nerdName: ATOM; IF argv.argc # 2 THEN GO TO Usage; nerdName ¬ Convert.AtomFromRope[argv[1] ! Convert.Error => GO TO Usage]; SELECT nerdName FROM $EtymologyNerd => spellingToolNerd ¬ SpellingToolServices.NerdType.EtymologyNerd; $WordNerd => spellingToolNerd ¬ SpellingToolServices.NerdType.WordNerd; ENDCASE => GO TO Usage; EXITS Die => result ¬ $Failure; Usage => RETURN[$Failure, "Usage: WordNerdReference NerdName"]; }; TryWord: Commander.CommandProc = TRUSTED { argv: CommanderOps.ArgumentVector ¬ CommanderOps.Parse[cmd ! CommanderOps.Failed => {msg ¬ errorMsg; GO TO Die}]; IF argv.argc # 2 THEN GO TO Usage; IF localDataCache=NIL OR localDataCache.first=NIL THEN GOTO NoData; [] ¬ localDataCache.first.bitTable.Lookup[LOOPHOLE[argv.s[1] ]]; EXITS Die => result ¬ $Failure; Usage => RETURN[$Failure, "Usage: TryWord word"]; NoData => RETURN[$Failure, "no local data"]; }; TRUSTED {Process.SetTimeout[@CleanlinessAchieved, Process.SecondsToTicks[30]]}; DefineMenu[]; Commander.Register[ key: "TryWord", proc: TryWord, doc: "Try to lookup a word."]; Commander.Register[ key: "SpellingTool", proc: SpellingToolCommand, doc: "Makes a Spelling Tool."]; Commander.Register[ key: "WNminKeyWords", proc: WordNerdMinKeyWordsCommand, doc: "Sets the parameters min key words for WordNerd."]; Commander.Register[ key: "WNminWords", proc: WordNerdMinWordsCommand, doc: "Sets the parameters min words for WordNerd."]; Commander.Register[ key: "WNmaxWords", proc: WordNerdMaxWordsCommand, doc: "Sets the parameters max words for WordNerd."]; Commander.Register[ key: "WNRef", proc: WordNerdReference, doc: "Sets the Nerd reference."]; END. SpellingToolImpl.mesa Copyright Σ 1985, 1987, 1992 by Xerox Corporation. All rights reserved. Last Edited by: Nix, December 2, 1983 11:57 am Wes Irish, January 18, 1989 4:40:37 pm PST gbb March 3, 1988 10:43:22 am PST Last tweaked by Mike Spreitzer on February 17, 1989 7:06:40 pm PST Jack Kent August 18, 1987 2:05:06 pm PDT Kent, March 3, 1987 3:39:03 pm PST Tim Diebert: January 26, 1987 5:01:14 pm PST Mike Spreitzer July 28, 1986 2:28:08 pm PDT Rick Beach, May 2, 1985 3:30:17 pm PDT Kenneth A. Pier, March 21, 1990 4:57 pm PST Doug Terry, March 4, 1991 1:36 pm PST Willie-s, May 22, 1992 5:08 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. word nerd parameters 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. VM.Touch[localData.bitTable.vmHandle.interval]; VM.Age[localData.bitTable.vmHandle.interval]; 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. IF ~Mail[name, password, reportStatisticsTo, "Spelling Tool statistics.", stats] THEN RETURN; IF mouseButton = blue THEN defaultMinKeyWords _ WordCount.CountRope[theWords].words; ViewerOps.SetOpenHeight[container, 6*emHeight + 8]; Testing code Κ6π–(cedarcode) style•NewlineDelimiter ˜code– "Cedar" stylešœ™K– "Cedar" stylešœ Οeœ=™HK™.K™*K™!KšœB™BKšœ(™(KšœΟk™"Kšœ)ž™,Kšœ(ž™+Kšœ#ž™&K™+K™%K™"—K˜šž ˜ Kšœžœ(˜2Kšœžœ'˜;Kšœ žœ˜(Kšœ žœ!˜3Kšœ žœ$˜4Kšœžœ$˜1Kšœžœžœ˜/Kšžœžœ žœ*˜WKšœ žœ˜*Kšœžœ˜*Kšžœ˜Kšœžœ˜Kšœžœ ˜«Kšœžœ=˜JKšœ˜K˜Kšœžœ˜0KšœžœŠ˜€Kšœžœp˜ˆKšœ žœ ˜Kšœ žœ˜,Kšœ žœ˜&Kšœ žœ;˜IKšœ žœ˜ Kšœ žœ«˜ΉKšœ žœ˜#Kšœ žœžœ ˜Kšœ žœT˜eKšœžœ&˜2Kšœ žœ˜&Kšœžœ ˜Kšœ žœU˜gKšœ žœ˜%Kšœ žœ.˜=Kšœžœ˜/—K˜KšΠlnœžœž˜šžœMžœžœυ˜αKšœžœžœ˜!Kšžœžœžœ˜Kš œ žœžœžœžœ˜Kšžœžœžœžœ˜Kšœžœ˜$Kšœ žœžœžœ˜"Kšœ žœ˜+K˜K˜Kšœžœžœžœ ˜(Kšœ žœžœ˜#šœžœžœ˜KšΟnœžœžœ˜Kšœ.žœ˜2Kšœ&žœžœ˜1šœžœžœ˜Kšœ8™8—šœžœ˜Kšœ@™@—šœ$žœ˜(K™H—Kšœ!žœ˜%Kšœž˜K˜—K˜Kšœžœžœžœ ˜&Kšœ žœžœ ˜!šœ žœžœ˜šœžœ˜ Kšœ™Kšœ™—Kšœ˜šœžœ˜KšœH™H—Kšœžœ˜Kšœžœžœ˜šœžœ˜ Kšœžœ%™G—Kšœžœ˜šœž˜ Kšœžœžœ%™?—Kšœ˜K˜Kšœžœ˜%—K˜K˜Kšœžœžœžœ ˜0˜KšœC™C—Kšœ&žœ˜8˜KšœB™B—Kšœžœ ˜(˜Kšœ,™,—Kšœžœ ˜ ˜Kšœ.™.—Kšœžœžœ9žœ ˜p˜Kšœ™—KšœT˜T˜Kšœ™—šœ žœ˜$Kšœž™"—˜K™—šœžœ˜Kšœ"žœž™>—šœžœžœ˜Kšœžœpžœ1™ͺ—˜Kšœ™—šœ(žœ™,K™—˜K™‰—Kšœžœ˜K˜Kšœ žœžœ˜˜2Kšœ5žœ(™`—K˜Kšœžœžœ˜šœ(žœ˜,KšœN™N—˜Kšœ;™;—Kšœ žœžœ˜Kšœžœ˜K˜Kšœžœžœ˜Kšœžœžœ˜Kšœ žœ˜Kšœžœ˜—Kšœ„žœ˜ŒK˜šœ$™$K™KšœR™R—Kšœ(˜(Kšœ,˜,šœžœžœ˜Kšœžœ4™:—˜Kšœ&™&—Kš œžœ˜˜KšœB™B—Kšœžœ˜˜Kšœ=™=—Kšœžœžœ˜˜K™—Kšœ žœ˜Kšœ žœ˜Kšœ žœ˜K˜YK˜Kšœžœ"˜.K˜K˜š œžœžœBžœ˜jKšœ‘žœk™€K˜*K˜%Kšœ žœ˜Kšœ Οsž‘œ˜K˜'K˜š œžœžœžœžœžœžœ žœžœžœ˜mK˜.šžœ žœžœ˜>Kšœžœžœ,˜>Kšœ žœ˜Kšžœ.žœ ˜Gš žœ žœžœžœžœž˜1Kš žœžœ žœ/žœžœ˜PKšžœžœžœ˜&Kšžœ˜—K˜—šžœ ž˜K˜%—K˜ š žœžœžœžœ'žœž˜aK˜—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šœ(žœ ˜8K˜K˜—šžœžœžœ˜K˜&Kšžœ˜—K˜'Kšžœ-™/šžœžœ˜K˜/K˜6K˜—šžœžœ˜šžœ žœ˜K˜>K˜6K˜—šžœ˜K˜KK˜K˜—K˜—K˜ršžœ žœ žœ˜$šžœžœ˜K˜-K˜6K˜—šžœ˜K˜K˜K˜—K˜IK˜K˜rK˜—K˜;šžœ žœ˜K˜IK˜6Kšœ@žœžœ˜YK˜K˜—šžœ˜šžœ,žœ˜4Kšœ-˜-Kšœ,˜,Kšœ'˜'K˜—šžœžœ˜KšžœD˜FK˜$K˜—šžœžœžœ˜KšžœB˜D—šžœ˜Kšžœ<žœ˜TK˜$K˜—K˜—Kšžœ+™-šž˜Kšœžœ˜—K˜K˜—K˜š œžœžœ0žœ˜fKšœAžœV™›K˜K˜ K˜K˜Kšœ žœ˜K˜*Kšœžœžœ˜'K˜š ’œžœžœžœžœ˜)Kšœžœ˜&š  œžœžœžœžœžœžœ žœžœžœžœ˜iKšœ žœ˜*Kšžœžœžœ˜,šžœžœ˜0KšœC˜CKšžœ0žœžœ9˜‚—Kšžœ˜K˜—Kšœ&žœžœ ˜>K˜šžœ˜Kšžœl˜pKšžœ'˜+—K˜—Kšœ8žœ˜JKšžœžœžœžœ˜$šžœž˜K˜—šžœ žœ˜šžœ žœ˜K˜K˜ K˜—šžœ˜K˜K˜ K˜—Kšœ žœ˜—K˜K˜K˜—š  œžœžœžœ˜AKšœžœžœ ˜3K˜K˜—š œžœžœžœ˜FKšœžœžœžœ˜-K˜—š  œžœžœžœ žœ˜EKšœžœ˜K˜K˜—š  œžœžœžœ˜MK™ Kšœ1˜1Kšœ žœ˜%Kšœ&žœ˜+Kšœ$žœ˜(K˜KšœBžœžœ˜OKšœžœ˜Kšœžœ1˜;Kšœ&žœ4˜`š   œžœžœžœžœ˜?šžœžœžœ˜K˜œKšœžœ˜—K˜—š   œžœžœžœ žœ˜3˜Kšœ9žœ˜?šž˜šœ˜šœ ˜ Kšœ/˜/Kšœ%˜%Kšžœ˜—Kšžœ"žœ˜+šžœ˜Kšœ,˜,Kšœ"˜"—Kšžœ˜—Kšžœ@˜B——K˜—š œžœžœžœ˜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š œžœžœžœžœΟc˜oKšœžœžœ˜(K˜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˜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˜RK˜ šžœ žœ˜K˜Kšžœžœžœ&˜JK˜—˜K˜K˜˜(Kšœ˜K˜K˜—K˜—Kšžœžœ(˜5K˜—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šžœ8žœ˜Mšœžœ(˜/šžœ ˜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˜—K˜—š  œžœžœ3˜OKšœQ™QKšœ žœ˜šžœ ž˜˜ Kšžœ8žœ˜LKšœ žœ(žœ žœ ˜TK˜—˜Kšœžœžœ6˜Ašžœžœž˜Kšœžœžœžœ˜ Kš œžœžœžœžœ˜$Kšžœžœ ˜—K˜—Kšžœžœ˜—šž˜Kš œžœžœžœžœ˜Kšœ!žœžœ˜9Kšœ˜Kšžœ˜—Kšžœ˜Kšžœ"˜$šž˜˜ Kšžœv˜x—˜ Kšžœ‘˜“——K˜K˜K˜—š   œžœžœžœ žœ™K˜š œžœ˜Kšœžœ˜Kšžœžœžœ/˜JK˜—šžœžœžœ˜!Kšœ žœžœ˜Kšœžœ˜ Kšœ˜šžœ ž˜˜Kšœ žœ1žœ žœ˜VK˜—˜Kšœ žœžœ˜šžœ/žœž˜>Kšœžœ˜Kšœžœžœ˜#Kšžœžœ˜—K˜—Kšžœžœ˜—šžœ žœžœ˜šžœ)žœžœž˜—K˜K˜—š’ œžœ˜$Kšžœžœžœ˜Kšœžœ ˜"Kšžœžœžœžœ˜š£œ˜Kšœ6žœžœ˜Bšžœžœ˜K˜,—K˜—K˜K˜—š’œžœ˜/Kšžœžœžœ˜Kšœ žœ˜*KšœA˜AKšžœžœžœžœ˜š£œ˜Kšœ6žœžœ˜Bšžœžœ˜K˜,—K˜—K˜K˜K˜—š’œžœ˜ Kšžœžœžœ˜Kšœžœžœžœ˜Cšžœžœ˜K˜,—K˜K˜—š’ œžœ˜$Kšžœžœžœ˜KšœJ˜JK˜K˜—K˜š’œžœžœžœ˜1Kšžœžœ˜Kšœžœ˜š ’Οbžœžœ žœžœ˜9K˜.Kšžœ˜K˜—š’œžœžœžœ˜:š œžœžœžœžœžœžœ žœžœžœžœžœ˜tK˜!K˜—Kšœ žœžœžœ˜2Kšœ˜K˜ KšœZžœ˜`šžœ žœžœ˜Kšžœ7˜9—šžœ˜Kšžœ;žœ˜PK˜—K˜—šœ9˜9KšžœF˜HKšžœ ˜—šžœ žœžœ˜KšžœF˜HKšžœ˜K˜—K˜Kšžœžœ˜Kšœ˜K˜K˜—š  œžœžœžœ˜6Kšžœžœ˜Kšœžœ˜š   ’₯žœžœ žœžœ˜?K˜3Kšžœ˜K˜—š’œžœžœžœ˜:š œžœžœžœžœžœžœ žœžœžœžœžœ˜tK˜!K˜—Kšœ žœžœžœ˜2Kšœ˜K˜ KšœZžœ˜`šžœ žœžœ˜Kšžœ7˜9—šžœ˜Kšžœ@žœ˜UK˜—K˜—šœ9˜9KšžœF˜HKšžœ˜—šžœ žœžœ˜KšžœF˜HKšžœ˜K˜—K˜#Kšžœžœ˜Kšœ˜K˜—š œžœžœžœ˜1Kšžœžœ˜Kšœ žœ˜Kšœžœ˜K•StartOfExpansion[stream: STREAM]šœžœ˜š’œžœžœ˜'K–[stream: STREAM]šœžœ˜&Kšœžœžœ˜$Kšœžœžœžœ ˜,–[stream: STREAM]šœžœ ˜8Kš œžœžœ#žœžœ˜H—šžœžœžœ˜#K–0[stream: STREAM, flushComments: BOOL _ TRUE]šœžœ9žœ˜GKšœ!žœ˜@K˜—Kšžœžœ:™TKšžœžœ*˜GK˜—š  ’₯žœžœ žœžœ˜;K˜™Kšžœ˜K˜—š’œžœžœžœ˜:Kšœ3žœžœžœ˜EK˜*Kšœ˜šžœžœ˜"Kšžœ7˜9—šžœ˜Kšžœ@žœ2˜vK˜—K˜—šœ9˜9KšžœG˜IKšžœ ˜—šžœ žœžœ˜KšžœG˜IKšžœ˜K˜—K˜Kšžœ žœ˜Kšœ˜K˜—K˜K˜š’œžœ˜ Kšžœžœžœ˜K˜ K˜š’œžœžœ˜2Kšœ'žœžœžœ˜;K˜K˜ K˜4K˜—š£œ˜Kšœ8žœ˜JK˜,Kšœ žœžœžœ˜2K˜—šž˜Kšœžœ-˜>—K˜K˜K˜—Kšœžœžœ˜Kšœžœžœ˜Kš œžœžœžœžœ˜+K˜š’œžœžœ!žœ˜Kš  œžœžœžœžœžœ žœ˜DK˜'K˜—š   œžœžœ žœžœžœ˜PKšžœžœžœžœ˜8K˜—Kšœžœ˜Kšœ žœ˜(Kšœžœ*˜?Kšœžœ˜Kšœžœ˜šžœ žœžœ˜KšžœD˜FKšžœ˜K˜—K˜.K˜K˜˜%KšœP˜P—Kšžœžœžœ#˜^šžœžœžœ˜Kšœžœ0˜EK˜—K˜ K˜Kšœ-˜-šžœžœžœ˜K˜ Kšœ!žœ˜&Kšœ‘˜‘KšžœJžœ˜gK˜—šžœ˜šžœ&žœžœž˜9Kšœžœ˜)šžœžœžœ˜7šžœ žœžœ˜(KšžœW˜YKšžœ˜K˜—K˜K˜5Kšœ'žœ˜,K˜—šœ˜Kšœ˜Kšœ˜šœ˜Kšœ˜Kšœ žœžœ žœ ˜žœ-žœ˜K˜FKšœ9žœžœžœ˜hKšœkžœ˜ršžœ˜ Kšœžœ?˜RKšœ˜—K˜K™3K˜&K˜&K˜&K˜K˜—K˜š’ œžœžœ˜ šžœž˜Kšž œ˜Kšžœ˜Kšžœ˜—K˜K˜—š  œžœ žœžœ žœ žœ˜GKšœžœ˜ K˜ Kšœ žœ˜šžœ"ž˜)Kšœ0žœ˜5Kšœ žœ˜Kšžœ˜—K˜K˜—š’œžœ#˜;Kšžœžœžœ˜Kšœžœ˜-K˜KKšœ5žœ˜9KšœSžœ ˜dKšœžœ˜šžœ7žœžœž˜Kšžœžœ˜'Kšžœž˜šžœ˜Kš œžœžœžœžœ8˜zKšžœžœžœ*žœ˜RK˜ Kšœ˜——Kšžœ˜—šœžœžœ˜5šœžœ ˜*K˜K˜#—K˜K˜—K˜MKšœFžœ˜Lšž˜šž˜Kšœ-žœ˜3šžœ˜Kšœ!˜!Kšœ˜——Kšžœ˜—Kšž œ˜K˜K˜—š’œ˜.šžœžœ˜K˜6Kšžœžœ˜9K˜—K˜K˜—š œ˜5˜:Kšœ*žœžœ˜6—šžœ ž˜˜Kšžœ@˜BK˜—˜Kšœ=žœžœ˜JK˜—Kšžœžœžœ˜—šžœ˜K˜Kšœ žœ1˜@—K˜—K˜š œ˜2˜:Kšœ*žœžœ˜6—šžœ ž˜˜Kšžœ:˜