<> <> <> <> <> <> <> <> <> <> <> <> <> <> 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, <<|-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: 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]; <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: 2]; <<|-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 ¬ 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; <<|-TRUE iff localListName in globalAuxilliaryWordlistNames.>> <> 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.