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
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[];
the property of document's root node that gives wordlist file names
localAuxilliaryWordlistNamesProperty: ATOM = $Wordlists;
the property of document's root node that gives the local wordlist
localWordlistProperty: ATOM = $Wordlist;
this names the wordlist local to a document.
localListName: ROPE = "(local)";
the property of a viewer that gives local data
viewerLocalDataKey: ATOM ¬ Atom.MakeAtom[IO.PutFR1["SpellingTool (started %g) Viewer to LocalData", IO.time[]]];
the icon
spellingToolIcon: Icons.IconFlavor = Icons.NewIconFromFile["SpellingTool.icons", 0];
The cache of files->bitTable
localDataCache: LocalDataList ¬ NIL;
|-Includes a ld iff ld.users # NIL
All the wordlists in the world
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.
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.
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.
The place for writing permanent messages, e.g. definitions.
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;
Parameters settable in User.Profile.
List of the word lists that the user would like merged in to the global word list.
globalAuxilliaryWordlistNames: RopeList;
globalAuxilliaryFileWordlistNames: RopeList;
localInEvery: BOOL ¬ FALSE;
|-TRUE iff localListName in globalAuxilliaryWordlistNames.
The name of the user's bit table file.
BitTableFilename: ROPE;
The number of seconds between writes to the user's word list file.
insertionInterval: INT ¬ 60;
Whether or not to strip s's off the ends of misspelled words.
pluralStripping: BOOL ¬ TRUE;
word nerd parameters
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] = {
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.
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];
VM.Touch[localData.bitTable.vmHandle.interval];
IF s.end.where = -1 THEN {
s.end.node ¬ TiogaOps.StepBackward[s.end.node];
s.end.where ¬ (TiogaOps.GetRope[s.end.node]).Size[]-1;
};
IF toEnd THEN {
IF forwards THEN {
s.end.node ¬ TiogaOps.LastWithin[TiogaOps.Root[s.start.node]];
s.end.where ¬ (TiogaOps.GetRope[s.end.node]).Size[]-1;
}
ELSE {
s.start.node ¬ SpellingToolShared.FirstWithin[TiogaOps.Root[s.start.node]];
s.start.where ¬ 0;
};
};
[misspelled, wordStart, wordEnd] ¬ MapWordsInSelection[s.start, s.end, 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];
};
};
VM.Age[localData.bitTable.vmHandle.interval];
EXITS
noSelection => NULL;
};
InsertWordOrInsertWordAndSpell: INTERNAL PROC [wl: Wordlist, andSpell, forwards, wrapAround: BOOL] = {
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.
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] = {
Get the right bit table & stuff.
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] = {
Utility for reading in the bit file portion of the word list.
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] = {
Utility for reading a token from the given stream into the given buffer.
UnPrintable: INTERNAL PROC [c: CHAR] RETURNS [unPrintable: BOOL] = INLINE {
How's this for device independence?
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] = {
Reads in an auxilliary word file and merges the words therein into the word list.
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"];
};
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"];
};
SaveInsertions: INTERNAL PROC [] = {
Saves the words the have been recently inserted into word lists in their files.
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]]] ];
IF ~Mail[name, password, reportStatisticsTo, "Spelling Tool statistics.", stats] THEN RETURN;
lastCheckCommands ¬ totalCheckCommands;
lastWordsCheckedCnt ¬ totalWordsCheckedCnt;
lastWordsProfitablyChecked ¬ totalWordsProfitablyChecked;
lastWordsMisspelledCnt ¬ totalWordsMisspelledCnt;
lastInsertionsCnt ¬ totalInsertionsCnt;
lastCorrectionsCnt ¬ totalCorrectionsCnt;
lastCorrectionsProposedCnt ¬ totalCorrectionsProposedCnt;
lastCorrectionsApplied ¬ totalCorrectionsApplied;
lastDefinitionsCnt ¬ totalDefinitionsCnt;
lastDefinitionsWorkedCount ¬ totalDefinitionsWorkedCount;
lastMaintainerNotifications ¬ totalMaintainerNotifications;
};
reportCount ¬ reportCount + 1;
};
};
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 mouseButton = blue THEN defaultMinKeyWords ← WordCount.CountRope[theWords].words;
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]];
};
ViewerOps.SetOpenHeight[container, 6*emHeight + 8];
Containers.ChildXBound[container, ts];
Containers.ChildYBound[container, ts];
ViewerOps.PaintViewer[container, all];
};
WaitTillClean: INTERNAL PROC = {
WHILE dirty DO
BROADCAST CleanlinessWanted;
WAIT CleanlinessAchieved;
ENDLOOP;
};
Remove: PROC [in, old: ROPE] RETURNS [removed: ROPE, changed: BOOL] = {
start: INT;
removed ¬ in;
changed ¬ FALSE;
WHILE (start ¬ removed.Find[old]) >= 0 DO
removed ¬ removed.Replace[start, old.Length[], NIL];
changed ¬ TRUE;
ENDLOOP;
};
InitializeProfile: ENTRY UserProfile.ProfileChangedProc = {
ENABLE UNWIND => NULL;
oldBitTableFilename: ROPE ¬ BitTableFilename;
oldGlobalAuxilliaryWordlistNames: RopeList ¬ globalAuxilliaryWordlistNames;
tail: RopeList ¬ globalAuxilliaryFileWordlistNames ¬ NIL;
globalAuxilliaryWordlistNames ¬ UserProfile.ListOfTokens["SpellingTool.Wordlists", LIST["(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"];
};
Testing code
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.