File: SpellingToolImpl.mesa
Last Edited by: Nix, December 2, 1983 11:57 am
Last Edited by: Spreitzer, March 2, 1985 1:44:59 pm PST
DIRECTORY
Atom USING [MakeAtom],
BitTableLookup USING [Failed, Insert, Lookup, Read, Table],
Commander USING [CommandProc, Register],
CommandTool USING [CurrentWorkingDirectory],
Containers USING [ChildXBound, ChildYBound, Create],
Basics USING [LongNumber, Comparison, BITAND],
FS USING [ComponentPositions, Error, ExpandName, StreamOpen],
GVSend USING [AddRecipient, AddToItem, Create, Handle, Send, SendFailed, StartSend, StartSendInfo, StartText],
Icons USING [IconFlavor, NewIconFromFile],
IO USING [Backup, CharClass, Close, EndOf, EndOfStream, GetChar, GetTokenRope, int, Put, PutChar, PutF, PutFR, PutRope, RIS, rope, SkipWhitespace, STREAM, time],
List USING [UniqueSort],
Menus USING [AppendMenuEntry, ChangeNumberOfLines, CreateEntry, CreateMenu, GetNumberOfLines, Menu, MenuEntry, MenuLine, MenuProc, MouseButton, ReplaceMenuEntry, SetLine],
MenusPrivate USING [menuHLeading, menuHSpace],
NodeProps USING [GetProp],
Process USING [Detach, DisableTimeout, SecondsToTicks, SetTimeout, Yield],
Rope USING [Cat, Compare, Equal, FromRefText, ROPE, Size, Substr],
SpellingCorrection USING [BruteForceCorrection],
SpellingToolDefinition USING [DefinitionButton, InitializeDictionaryServer, FinalizeDictionaryServer, DefinitionStats],
SpellingToolShared USING [FirstWithin, CheckMiddleOfWord, Selection, CorrectTiogaOpsCallWithLocks, MapWordsInSelection, ProcessSelection],
TEditScrolling USING [AutoScroll],
TEditOpsExtras USING [WorkingDirectoryFromViewer],
TextNode USING [Ref],
TiogaOps USING [Delete, FetchLooks, GetRope, InsertRope, LastWithin, NoSelection, Ref, Root, SaveForPaste, SelectionGrain, SetLooks, SetSelection, StepBackward],
TiogaOpsDefs USING [Location, Ref, Viewer],
TypeScript USING [TS, Create],
UserCredentials USING [Get],
UserProfile USING [Boolean, CallWhenProfileChanges, ListOfTokens, Number, ProfileChangedProc, Token],
VFonts USING [CharWidth, FontHeight, StringWidth],
ViewerBLT USING [ChangeNumberOfLines],
ViewerClasses USING [Viewer],
ViewerEvents USING [EventProc, EventRegistration, RegisterEventProc, UnRegisterEventProc, ViewerEvent],
ViewerIO USING [CreateViewerStreams],
ViewerOps USING [AddProp, BlinkIcon, CreateViewer, FetchProp, PaintViewer, SetOpenHeight],
VM USING [Age, Touch];
SpellingToolImpl:
CEDAR
MONITOR
IMPORTS Atom, Basics, BitTableLookup, Commander, CommandTool, Containers, FS, GVSend, Icons, IO, List, Menus, NodeProps, Process, Rope, SpellingCorrection, SpellingToolDefinition, SpellingToolShared, TEditOpsExtras, TEditScrolling, TiogaOps, TypeScript, UserCredentials, UserProfile, VFonts, ViewerBLT, ViewerEvents, ViewerIO, ViewerOps, VM =
BEGIN
OPEN SpellingToolShared;
ROPE: TYPE = Rope.ROPE;
RopeList: TYPE = LIST OF ROPE;
STREAM: TYPE = IO.STREAM;
Viewer: TYPE = ViewerClasses.Viewer;
ViewerList: TYPE = LIST OF Viewer;
LocalDataList: TYPE = LIST OF LocalData;
LocalData: TYPE = REF LocalDataRep;
LocalDataRep:
TYPE =
RECORD [
BitTableFilename: ROPE ← NIL,
globalAuxilliaryWordlistFilenames: RopeList ← NIL,
wDir:
ROPE ←
NIL,
For localAuxilliaryWordlistFilenamesRope.
localAuxilliaryWordlistFilenamesRope: ROPE ← NIL,
auxilliaryWordlistFilenames: RopeList ←
NIL,
|-Contains the alphabetized, duplicate-filtered union of local and globals.
bitTable: BitTableLookup.Table ← NIL,
users: ViewerList ← NIL
];
FileDataList: TYPE = LIST OF FileData;
FileData: TYPE = REF FileDataRep;
FileDataRep:
TYPE =
RECORD [
filename: ROPE,
localDatas: LocalDataList ←
NIL,
|-Includes a ld iff ld.users # NIL & ld.auxilliaryWordlistFilenames includes me
wordsAddedList: RopeList ← NIL,
menuEntry: Menus.MenuEntry ←
NIL
|-# NIL iff localDatas#NIL & IsLocal[filename]
];
installationDirectory: ROPE ← CommandTool.CurrentWorkingDirectory[];
the property of document's root node that gives wordlist file names
localAuxilliaryWordlistFilenamesProperty: ATOM = $Wordlists;
the property of a viewer that gives local data
viewerLocalDataKey: ATOM ← Atom.MakeAtom[IO.PutFR["SpellingTool (started %g) Viewer to LocalData", IO.time[]]];
spellingToolIcon: Icons.IconFlavor = Icons.NewIconFromFile["SpellingTool.icons", 0];
The cache of files->bitTable
localDataCache: LocalDataList ←
NIL;
|-Includes a ld iff ld.users # NIL
The associated with each file
fileDatas: FileDataList ←
NIL;
|-Includes a fd iff fd.localDatas#NIL or fd.wordsAddedList#NIL
dirty:
BOOL ←
FALSE;
|-TRUE when some FileData have non-empty wordsAddedList. A background process will write these additions to the file, NIL-out the wordsAddedList, and reset the dirty big.
Handle for the word list.
globalBitTable: BitTableLookup.Table ← NIL;
Must now be local to document.
toolExists: BOOL ← FALSE;
toolMenu: Menus.Menu ← Menus.CreateMenu[lines: 1];
|-Includes an insert button for fd iff fd.localDatas#NIL & IsLocal[fd.filename]
The place for writing permanent messages, e.g. definitions.
permMessage: PUBLIC STREAM;
permMessageIn: STREAM;
reportStatistics: BOOL ← FALSE;
reportStatisticsTo: ROPE ← "Nix.pa";
reportCount: INT ← 0;
totalCheckCommands, totalCorrectionsApplied, totalCorrectionsCnt, totalCorrectionsProposedCnt, totalDefinitionsCnt, totalDefinitionsWorkedCount, totalInsertionsCnt, totalMaintainerNotifications, totalWordsCheckedCnt, totalWordsMisspelledCnt, totalWordsProfitablyChecked: INT ← 0;
lastCheckCommands, lastCorrectionsApplied, lastCorrectionsCnt, lastCorrectionsProposedCnt, lastDefinitionsCnt, lastDefinitionsWorkedCount, lastInsertionsCnt, lastMaintainerNotifications, lastWordsCheckedCnt, lastWordsMisspelledCnt, lastWordsProfitablyChecked: INT ← 0;
Parameters settable in User.Profile.
List of the names of the word list files that the user would like merged in to the global word list.
globalAuxilliaryWordlistFilenames: RopeList;
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;
SpellCheckWithin:
INTERNAL
PROC [s: Selection, 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]
RETURNS [misspelled:
BOOL] = {
misspelled ← ~localData.bitTable.Lookup[word];
IF misspelled
THEN
misspelled ← HandleMisspelling[word];
wordsChecked ← wordsChecked + 1;
IF
LOOPHOLE[Basics.
BITAND[511,
LOOPHOLE[wordsChecked, Basics.LongNumber].lowbits],
CARDINAL] = 0B
THEN
Process.Yield[];
};
HandleMisspelling:
INTERNAL
PROC [word:
REF
TEXT]
RETURNS [misspelled:
BOOL ←
TRUE] = {
s: INT ← word.length;
lc, sc: CHAR;
IF ~pluralStripping THEN RETURN;
IF s = 1
THEN {
misspelled ← FALSE;
RETURN;
};
IF s <= 4 THEN RETURN;
lc ← word[s-1];
sc ← word[s-2];
IF lc = 's
THEN {
IF sc = 's THEN RETURN;
IF sc = '\'
THEN {
word.length ← s - 2;
misspelled ← ~localData.bitTable.Lookup[word];
word.length ← s + 2;
}
ELSE {
word.length ← s - 1;
misspelled ← ~localData.bitTable.Lookup[word];
word.length ← s + 1;
};
};
};
Locker:
INTERNAL
PROC [root: TiogaOpsDefs.Ref] = {
TiogaOps.SetSelection[s.viewer, wordStart, wordEnd, char, TRUE, TRUE, primary];
};
IF
NOT Valid[localData]
THEN {
permMessage.PutRope["Can't check.\n"];
RETURN};
VM.Touch[localData.bitTable.vmHandle.interval];
IF s.end.where = -1
THEN {
s.end.node ← TiogaOps.StepBackward[s.end.node];
s.end.where ← (TiogaOps.GetRope[s.end.node]).Size[]-1;
};
IF toEnd
THEN {
IF forwards
THEN {
s.end.node ← TiogaOps.LastWithin[TiogaOps.Root[s.start.node]];
s.end.where ← (TiogaOps.GetRope[s.end.node]).Size[]-1;
}
ELSE {
s.start.node ← SpellingToolShared.FirstWithin[TiogaOps.Root[s.start.node]];
s.start.where ← 0;
};
};
[misspelled, wordStart, wordEnd] ← MapWordsInSelection[s.start, s.end, SpellingCheckerProc, forwards];
IF ~misspelled
AND wrapAround
THEN {
IF loc.where = 0
THEN {
s.end.node ← TiogaOps.StepBackward[loc.node];
s.end.where ← (TiogaOps.GetRope[s.end.node]).Size[]-1;
}
ELSE {
s.end.node ← loc.node;
s.end.where ← loc.where - 1;
};
s.start.node ← SpellingToolShared.FirstWithin[TiogaOps.Root[s.end.node]];
s.start.where ← 0;
[misspelled, wordStart, wordEnd] ← MapWordsInSelection[s.start, s.end, SpellingCheckerProc, forwards];
};
totalWordsCheckedCnt ← totalWordsCheckedCnt + wordsChecked;
IF misspelled
THEN {
totalWordsProfitablyChecked ← totalWordsProfitablyChecked + wordsChecked;
totalWordsMisspelledCnt ← totalWordsMisspelledCnt + 1;
CorrectTiogaOpsCallWithLocks[Locker ! TiogaOps.NoSelection => {Locker[NIL]; GOTO noSelection}];
TEditScrolling.AutoScroll[s.viewer];
ProposeCorrections[localData, Rope.Substr[TiogaOps.GetRope[wordStart.node], wordStart.where, wordEnd.where-wordStart.where+1]];
}
ELSE {
IF Menus.GetNumberOfLines[container.menu] # 1
THEN {
Menus.ChangeNumberOfLines[container.menu, 1];
ViewerBLT.ChangeNumberOfLines[container, 1];
ViewerOps.PaintViewer[container, menu];
};
IF wordsChecked = 0
THEN {
IO.PutF[permMessage, "The selection did not contain any words.\n"];
ViewerOps.BlinkIcon[s.viewer, 1, 1];
}
ELSE
IF wordsChecked = 1
THEN
IO.PutF[permMessage, "The selected word is correctly spelled.\n"]
ELSE {
IO.PutF[permMessage, "No misspellings found in %g words.\n", IO.int[wordsChecked]];
ViewerOps.BlinkIcon[s.viewer, 1, 1];
};
};
VM.Age[localData.bitTable.vmHandle.interval];
EXITS
noSelection => NULL;
};
InsertWordOrInsertWordAndSpell:
INTERNAL
PROC [fd: FileData, 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;
localData: LocalData;
misspelled: BOOL;
wordStart, wordEnd: TiogaOpsDefs.Location;
auxilliaryWordlistFilename: ROPE ← NIL;
Locker:
INTERNAL
PROC [root: TiogaOpsDefs.Ref] = {
WordCollector:
INTERNAL
PROC [word:
REF
TEXT]
RETURNS [stop:
BOOL ←
FALSE] = {
AddWord[fd, word, Rope.FromRefText[word]];
};
s ← ProcessSelection[FALSE, FALSE, forwards].s;
localData ← GetLocalData[s];
IF Valid[localData]
THEN [misspelled, wordStart, wordEnd] ← MapWordsInSelection[s.start, s.end, WordCollector, forwards]
ELSE permMessage.PutRope["Can't check.\n"];
};
CorrectTiogaOpsCallWithLocks[Locker ! TiogaOps.NoSelection => GOTO noSelection];
IF NOT Valid[localData] THEN RETURN;
IF insertionInterval = 0
THEN
SaveInsertions[];
IF andSpell
THEN {
IF forwards
THEN {
s.start.node ← s.end.node;
s.start.where ← s.end.where + 1;
}
ELSE {
s.end.node ← s.start.node;
s.end.where ← s.start.where - 1;
};
SpellCheckWithin[s, TRUE, forwards, wrapAround];
};
EXITS
noSelection => IO.PutF[permMessage, "Make a selection.\n"];
};
TextNodeRoot:
PROC [tiogaOpsNode: TiogaOps.Ref]
RETURNS [textNode: TextNode.Ref] = {
tiogaOpsRoot: TiogaOps.Ref ← TiogaOps.Root[tiogaOpsNode];
TRUSTED {textNode ← LOOPHOLE[tiogaOpsRoot]};
};
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.
localAuxilliaryWordlistFilenamesRope: ROPE ← NARROW[NodeProps.GetProp[TextNodeRoot[s.start.node], localAuxilliaryWordlistFilenamesProperty]];
auxilliaryWordlistFilenames: RopeList ← NIL;
listComputed: BOOL ← FALSE;
WholeList:
PROC
RETURNS [rl: RopeList] =
INLINE {
IF
NOT listComputed
THEN {
auxilliaryWordlistFilenames ← ParseToList[wDir, localAuxilliaryWordlistFilenamesRope, globalAuxilliaryWordlistFilenames];
listComputed ← TRUE};
rl ← auxilliaryWordlistFilenames};
wDir: ROPE ← TEditOpsExtras.WorkingDirectoryFromViewer[s.viewer];
ValidCache:
INTERNAL
PROC
RETURNS [valid:
BOOL] = {
valid ←
Rope.Equal[localData.BitTableFilename, BitTableFilename, FALSE]
AND
(
(Rope.Equal[
localData.localAuxilliaryWordlistFilenamesRope,
localAuxilliaryWordlistFilenamesRope,
FALSE]
AND Rope.Equal[localData.wDir, wDir, FALSE]
AND ListEqual[
localData.globalAuxilliaryWordlistFilenames,
globalAuxilliaryWordlistFilenames])
OR ListEqual[WholeList[], localData.auxilliaryWordlistFilenames]);
};
oldLocalData: LocalData;
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];
};
oldLocalData ← localData ← NARROW[ViewerOps.FetchProp[s.viewer, viewerLocalDataKey]];
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, localAuxilliaryWordlistFilenamesRope, auxilliaryWordlistFilenames];
[] ← UseIt[];
};
};
ParseToList:
PROC [wDir, rope:
ROPE, andUnion: RopeList]
RETURNS [list: RopeList] = {
Add:
PROC [r:
ROPE, l: RopeList]
RETURNS [new: RopeList] = {
IF l = NIL THEN RETURN [LIST[r]];
SELECT Rope.Compare[r, l.first,
FALSE]
FROM
less => RETURN [CONS[r, l]];
equal => RETURN [l];
greater => RETURN [CONS[l.first, Add[r, l.rest]]];
ENDCASE => ERROR;
};
stream: STREAM ← IO.RIS[rope];
list ← andUnion;
FOR i:
INT ← stream.SkipWhitespace[], stream.SkipWhitespace[]
WHILE
NOT stream.EndOf[]
DO
raw: ROPE ← stream.GetTokenRope[SepByWhite].token;
r: ROPE ← FS.ExpandName[name: raw, wDir: wDir].fullFName;
list ← Add[r, list];
ENDLOOP;
stream.Close[];
};
ListEqual:
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;
};
ComputeLocalData:
INTERNAL
PROC [wDir, localAuxilliaryWordlistFilenamesRope:
ROPE, auxilliaryWordlistFilenames: RopeList]
RETURNS [localData: LocalData] = {
success: BOOL ← TRUE;
localData ←
NEW [LocalDataRep ← [
BitTableFilename: BitTableFilename,
globalAuxilliaryWordlistFilenames: globalAuxilliaryWordlistFilenames,
wDir: wDir,
localAuxilliaryWordlistFilenamesRope: localAuxilliaryWordlistFilenamesRope,
auxilliaryWordlistFilenames: auxilliaryWordlistFilenames]];
[success, localData.bitTable] ← GetBitFile[localData.BitTableFilename];
IF NOT success THEN RETURN [NIL];
FOR f: RopeList ← localData.auxilliaryWordlistFilenames, f.rest
UNTIL f =
NIL
DO
fd: FileData ← FindFileData[f.first];
fd.localDatas ← CONS[localData, fd.localDatas];
MergeWordFile[f.first, localData.bitTable];
ENDLOOP;
localDataCache ← CONS[localData, localDataCache];
};
FindFileData:
INTERNAL
PROC [filename:
ROPE]
RETURNS [fd: FileData] = {
FOR fdl: FileDataList ← fileDatas, fdl.rest
WHILE fdl #
NIL
DO
IF fdl.first.filename.Equal[filename, FALSE] THEN RETURN [fdl.first];
ENDLOOP;
fd ← NEW [FileDataRep ← [filename: filename]];
fileDatas ← CONS[fd, fileDatas];
IF toolExists THEN MaybeAddWordlistInserter[fd, TRUE];
};
MaybeAddWordlistInserter:
PROC [fd: FileData, paint:
BOOL] = {
fullFName, shortName, ext: ROPE;
cp: FS.ComponentPositions;
IF fd.menuEntry # NIL THEN ERROR;
[fullFName, cp, ] ← FS.ExpandName[fd.filename];
IF cp.server.length = 0
THEN {
shortName ← fullFName.Substr[start: cp.base.start, len: cp.base.length];
ext ← fullFName.Substr[start: cp.ext.start, len: cp.ext.length];
IF NOT ext.Equal["Wordlist", FALSE] THEN shortName ← shortName.Cat[".", ext];
Menus.AppendMenuEntry[
menu: toolMenu,
line: 0,
entry: fd.menuEntry ← Menus.CreateEntry[
name: shortName,
proc: InsertWord,
clientData: fd]
];
IF paint THEN ViewerOps.PaintViewer[container, menu];
};
};
SepByWhite:
PROC [char:
CHAR]
RETURNS [
IO.CharClass]
--IO.BreakProc-- = {
RETURN [
SELECT char
FROM
IN [0C .. ' ] => sepr,
ENDCASE => other];
};
generalDestroyRegistration: ViewerEvents.EventRegistration ← ViewerEvents.RegisterEventProc[proc: NoteDestruction, event: destroy];
NoteDestruction:
ENTRY
PROC [viewer: Viewer, event: ViewerEvents.ViewerEvent, before:
BOOL]
RETURNS [abort:
BOOL ←
FALSE]
--ViewerEvents.EventProc-- = {
ENABLE UNWIND => NULL;
localData: LocalData ← NARROW[ViewerOps.FetchProp[viewer, viewerLocalDataKey]];
IF event = destroy
AND localData #
NIL
THEN {
ViewerOps.AddProp[viewer, viewerLocalDataKey, NIL];
NoteNonUse[localData, viewer];
};
};
NoteNonUse:
INTERNAL
PROC [localData: LocalData, user: Viewer] = {
localData.users ← FilterViewerList[user, localData.users];
IF localData.users =
NIL
THEN {
myFileDatas: FileDataList ← NIL;
FOR rl: RopeList ← localData.auxilliaryWordlistFilenames, rl.rest
WHILE rl #
NIL
DO
myFileDatas ← CONS[FindFileData[rl.first], myFileDatas];
ENDLOOP;
FOR myFileDatas ← myFileDatas, myFileDatas.rest
WHILE myFileDatas #
NIL
DO
fd: FileData ← myFileDatas.first;
fd.localDatas ← FilterLocalDataList[localData, fd.localDatas];
IF fd.localDatas =
NIL
THEN {
IF fd.menuEntry # NIL THEN Unenter[fd, TRUE];
IF fd.wordsAddedList = NIL THEN fileDatas ← FilterFileDataList[fd, fileDatas];
};
ENDLOOP;
localDataCache ← FilterLocalDataList[localData, localDataCache];
};
};
Unenter:
PROC [fd: FileData, paint:
BOOL] = {
Menus.ReplaceMenuEntry[menu: toolMenu, oldEntry: fd.menuEntry, newEntry: NIL];
fd.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;
};
FilterFileDataList:
PROC [member: FileData, oldList: FileDataList]
RETURNS [newList: FileDataList] = {
last: FileDataList ← NIL;
newList ← oldList;
FOR this: FileDataList ← 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.PutF[permMessage, "Reading word list from \"%g\"...", IO.rope[fileName]];
s ←
FS.StreamOpen[fileName !
FS.Error => {
permMessage.PutRope[" file could not be read.\n"];
GOTO openFailed}];
bitTable ← BitTableLookup.Read[s !
BitTableLookup.Failed => {
permMessage.PutRope[
SELECT why
FROM
TooLarge => " bit table too large.\n",
BadTable => " contained bad table.\n",
ENDCASE => ERROR];
GOTO readFailed}];
s.Close[];
succeeded ← TRUE;
IO.Put[permMessage, IO.rope[" done.\n"]];
EXITS
openFailed => NULL;
readFailed => NULL;
};
AddWord:
INTERNAL
PROC [fd: FileData, wordAsText:
REF
TEXT, wordAsRope:
ROPE] = {
totalInsertionsCnt ← totalInsertionsCnt + 1;
IO.PutF[permMessage, "\"%g\" inserted in %g.\n", IO.rope[wordAsRope], IO.rope[fd.filename]];
fd.wordsAddedList ← CONS[wordAsRope, fd.wordsAddedList];
dirty ← TRUE;
FOR ldl: LocalDataList ← fd.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 ← c IN ['\000 .. '\040];
};
c: CHAR;
w ← word;
WHILE UnPrintable[c ← s.GetChar[]] DO ENDLOOP;
s.Backup[c];
w.length ← w.maxLength;
FOR i:
CARDINAL ← 0, i+1
DO
c ← s.GetChar[ ! IO.EndOfStream => GOTO done];
IF UnPrintable[c] THEN GOTO done;
IF i >= w.maxLength
THEN {
w ← NEW[TEXT[2*w.maxLength+1]];
w.length ← w.maxLength;
};
w[i] ← c;
REPEAT
done => w.length ← i;
ENDLOOP;
MergeWordFile:
INTERNAL
PROC [fileName:
ROPE, bitTable: BitTableLookup.Table] = {
Reads in an auxilliary word file and merges the words therein into the word list.
wordFile: STREAM;
IO.PutF[permMessage, "Adding words from file \"%g\"...", IO.rope[fileName]];
wordFile ← FS.StreamOpen[fileName ! FS.Error => GOTO openFailed];
DO
word: REF TEXT ← NEW[TEXT[30]];
word ← GetToken[wordFile, word ! IO.EndOfStream => EXIT];
bitTable.Insert[word];
ENDLOOP;
IO.Close[wordFile];
IO.Put[permMessage, IO.rope[" done.\n"]];
EXITS
openFailed =>
IO.Put[permMessage, IO.rope[" file did not exist.\nThis file will be created automatically if you insert words into it.\n"]];
};
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: FileDataList ← fileDatas, fdl.rest
WHILE fdl #
NIL
DO
fd: FileData ← fdl.first;
NILList:
PROC = {
fd.wordsAddedList ← NIL;
IF fd.localDatas = NIL THEN fileDatas ← FilterFileDataList[fd, fileDatas];
};
IF fd.wordsAddedList #
NIL
THEN {
wordFile: STREAM ← NIL;
lock: BOOL;
wordFile ← FS.StreamOpen[fd.filename, $append ! FS.Error => {lock ← error.group = lock; CONTINUE}];
IF wordFile #
NIL
THEN {
FOR w: RopeList ← fd.wordsAddedList, w.rest
UNTIL w =
NIL
DO
wordFile.Put[IO.rope[w.first]];
wordFile.PutChar['\n];
ENDLOOP;
wordFile.Close[];
NILList[];
}
ELSE {
IO.PutF[permMessage, "The insertion save file \"%g\" could not be written", IO.rope[fd.filename]];
IF lock
THEN
--try again later-- {
IO.PutRope[permMessage, " due to lock conflict --- will try again later.\n"];
dirty ← TRUE;
}
ELSE {
IO.PutRope[permMessage, "; insertions lost.\n"];
NILList[];
};
};
};
ENDLOOP;
DoStats[];
};
DoStats:
INTERNAL
PROC = {
[totalDefinitionsCnt, totalDefinitionsWorkedCount] ← SpellingToolDefinition.DefinitionStats[];
IF reportStatistics
THEN
IF (totalCheckCommands # lastCheckCommands)
OR
(totalWordsCheckedCnt # lastWordsCheckedCnt) OR
(totalWordsProfitablyChecked # lastWordsProfitablyChecked) OR
(totalWordsMisspelledCnt # lastWordsMisspelledCnt) OR
(totalInsertionsCnt # lastInsertionsCnt) OR
(totalCorrectionsCnt # lastCorrectionsCnt) OR
(totalCorrectionsProposedCnt # lastCorrectionsProposedCnt) OR
(totalCorrectionsApplied # lastCorrectionsApplied) OR
(totalDefinitionsCnt # lastDefinitionsCnt) OR
(totalDefinitionsWorkedCount # lastDefinitionsWorkedCount) OR
(totalMaintainerNotifications # lastMaintainerNotifications) THEN {
IF reportCount >= 60
THEN {
name, password, stats: Rope.ROPE;
reportCount ← 0;
[name, password] ← UserCredentials.Get[];
stats ← Rope.Cat[
IO.PutFR["\nNumber of searches: %g\nNumber of words checked: %g\nNumber of words profitably checked: %g\nMisspellings found: %g\nInsertions made: %g\n", IO.int[totalCheckCommands], IO.int[totalWordsCheckedCnt], IO.int[totalWordsProfitablyChecked], IO.int[totalWordsMisspelledCnt], IO.int[totalInsertionsCnt]],
IO.PutFR["Words corrected: %g\n", IO.int[totalCorrectionsCnt]],
IO.PutFR["Corrections proposed: %g\nCorrections accepted: %g\nDefinitions requested: %g\nDefinitions given: %g\nNotifications sent: %g\n", IO.int[totalCorrectionsProposedCnt], IO.int[totalCorrectionsApplied], IO.int[totalDefinitionsCnt], IO.int[totalDefinitionsWorkedCount], IO.int[totalMaintainerNotifications]]];
IF ~Mail[name, password, reportStatisticsTo, "Spelling Tool statistics.", stats] THEN RETURN;
lastCheckCommands ← totalCheckCommands;
lastWordsCheckedCnt ← totalWordsCheckedCnt;
lastWordsProfitablyChecked ← totalWordsProfitablyChecked;
lastWordsMisspelledCnt ← totalWordsMisspelledCnt;
lastInsertionsCnt ← totalInsertionsCnt;
lastCorrectionsCnt ← totalCorrectionsCnt;
lastCorrectionsProposedCnt ← totalCorrectionsProposedCnt;
lastCorrectionsApplied ← totalCorrectionsApplied;
lastDefinitionsCnt ← totalDefinitionsCnt;
lastDefinitionsWorkedCount ← totalDefinitionsWorkedCount;
lastMaintainerNotifications ← totalMaintainerNotifications;
};
reportCount ← reportCount + 1;
};
};
Mail:
INTERNAL
PROC[sender, password, recipient, subject, message:
ROPE]
RETURNS[success:
BOOLEAN ←
FALSE] =
BEGIN
grapevine: GVSend.Handle;
results: GVSend.StartSendInfo;
grapevine ← GVSend.Create[];
scope of GVSend.SendFailed
BEGIN ENABLE GVSend.SendFailed => CONTINUE;
results ← grapevine.StartSend[password, sender, NIL, FALSE];
IF results = ok
THEN {
grapevine.AddRecipient[recipient];
grapevine.StartText[];
grapevine.AddToItem[IO.PutFR["Date: %g\nFrom: %g\nSubject: %g\nTo: %g\n\n%g", IO.time[], IO.rope[sender], IO.rope[subject], IO.rope[recipient], IO.rope[message]]];
grapevine.Send[]};
RETURN[TRUE];
END;
CleanlinessWanted: CONDITION;
CleanlinessAchieved: CONDITION;
MakeAdditionsPermanent:
ENTRY
PROC =
TRUSTED {
ENABLE UNWIND => NULL;
DO
IF insertionInterval > 0
THEN
Process.SetTimeout[@CleanlinessWanted, Process.SecondsToTicks[insertionInterval]]
ELSE
Process.DisableTimeout[@CleanlinessWanted];
SaveInsertions[];
BROADCAST CleanlinessAchieved;
IF NOT toolExists THEN EXIT;
WAIT CleanlinessWanted;
ENDLOOP;
};
CheckSpelling:
ENTRY Menus.MenuProc = {
ENABLE UNWIND => NULL;
s: Selection;
wasExtended: BOOL;
Locker:
INTERNAL
PROC [root: TiogaOpsDefs.Ref] = {
[s, wasExtended] ← ProcessSelection[FALSE, TRUE, TRUE];
};
--IF GotBitTable[] THEN-- {
CorrectTiogaOpsCallWithLocks[Locker ! TiogaOps.NoSelection => GOTO noSelection];
totalCheckCommands ← totalCheckCommands + 1;
IF mouseButton = blue
AND ~wasExtended
THEN {
s.start.node ← s.end.node;
s.start.where ← s.end.where + 1;
SpellCheckWithin[s, TRUE, TRUE, TRUE];
}
ELSE
SpellCheckWithin[s, FALSE, TRUE, wasExtended];
};
EXITS
noSelection => IO.PutF[permMessage, "Make a selection.\n"];
};
InsertWord:
ENTRY Menus.MenuProc = {
ENABLE UNWIND => NULL;
fd: FileData ← NARROW[clientData];
--IF GotBitTable[] THEN-- {
InsertWordOrInsertWordAndSpell[fd, mouseButton # red, TRUE, TRUE];
IF mouseButton # red
THEN
totalCheckCommands ← totalCheckCommands + 1;
};
};
InsertHead:
ENTRY Menus.MenuProc = {
ENABLE UNWIND => NULL;
permMessage.PutRope["Click on name of wordlist you wish to insert in.\n"];
};
Ignore:
ENTRY Menus.MenuProc = {
ENABLE UNWIND => NULL;
s: Selection;
Locker:
INTERNAL
PROC [root: TiogaOpsDefs.Ref] = {
s ← ProcessSelection[FALSE, FALSE, TRUE].s;
s.start.node ← s.end.node;
s.start.where ← s.end.where + 1;
SpellingToolShared.CheckMiddleOfWord[s];
};
--IF GotBitTable[] THEN-- {
CorrectTiogaOpsCallWithLocks[Locker ! TiogaOps.NoSelection => GOTO noSelection];
totalCheckCommands ← totalCheckCommands + 1;
SpellCheckWithin[s, TRUE, TRUE, TRUE];
};
EXITS
noSelection => IO.PutF[permMessage, "Make a selection.\n"];
};
theWordCorrected: ROPE ← NIL;
lastWordCorrected: ROPE ← NIL;
correctionBuffer: REF TEXT ← NEW[TEXT[20]];
ProposeCorrections:
INTERNAL
PROC [localData: LocalData, theWord:
ROPE] = {
WordChecker:
INTERNAL
PROC [w:
REF
TEXT]
RETURNS [inTable:
BOOL] = {
inTable ← localData.bitTable.Lookup[w];
};
CompareWords:
INTERNAL
PROC[ref1, ref2:
REF
ANY]
RETURNS [Basics.Comparison] = {
RETURN[Rope.Compare[NARROW[ref1], NARROW[ref2], FALSE]];
};
numCorrections: INT ← 0;
viewerWidth: INTEGER ← container.ww - 6;
oldMenuLines: INTEGER ← Menus.GetNumberOfLines[container.menu];
widthLeft, lineNum: INTEGER;
correctionList: RopeList ← NIL;
IF theWord =
NIL
THEN {
IO.PutF[permMessage, "No word selected, so no correction done.\n"];
RETURN;
};
totalCorrectionsCnt ← totalCorrectionsCnt + 1;
lastWordCorrected ← theWord;
theWordCorrected ← theWord;
[correctionList, correctionBuffer] ←
SpellingCorrection.BruteForceCorrection[theWord, WordChecker, correctionBuffer];
TRUSTED {correctionList ← LOOPHOLE[List.UniqueSort[LOOPHOLE[correctionList], CompareWords]];};
IF correctionList #
NIL
THEN {
correctionList ← CONS[Rope.Cat[theWord, " --> "], correctionList];
};
lineNum ← 0;
widthLeft ← 0;
Menus.ChangeNumberOfLines[container.menu, 2];
IF correctionList =
NIL
THEN {
lineNum ← 1;
Menus.SetLine[container.menu, 1, NIL];
Menus.AppendMenuEntry[line: 1, menu: container.menu,
entry: Menus.CreateEntry[name: "No corrections for this word.", proc: NoCorrectionButton]];
IO.PutF[permMessage, "\"%g\" may be misspelled; sorry, no corrections.\n", IO.rope[theWordCorrected]];
}
ELSE {
FOR c: RopeList ← correctionList, c.rest
UNTIL c =
NIL
DO
width: INT ← VFonts.StringWidth[c.first];
IF width > widthLeft
AND widthLeft < viewerWidth
THEN {
IF ~(lineNum+2
IN Menus.MenuLine)
THEN {
IO.PutF[permMessage, "There are more corrections, but they can't fit in the menu.\n"];
EXIT;
};
lineNum ← lineNum + 1;
widthLeft ← viewerWidth - MenusPrivate.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 - MenusPrivate.menuHSpace;
numCorrections ← numCorrections + 1;
ENDLOOP;
IO.PutF[permMessage, "\"%g\" may be misspelled; %g correction buttons above.\n", IO.rope[theWordCorrected], IO.int[numCorrections-1]];
};
IF lineNum+1 # oldMenuLines
THEN
ViewerBLT.ChangeNumberOfLines[container, lineNum+1];
totalCorrectionsProposedCnt ← totalCorrectionsProposedCnt + numCorrections - 1;
ViewerOps.PaintViewer[container, menu];
};
ApplyCorrectionButton:
ENTRY
PROC [parent:
REF
ANY, clientData:
REF
ANY ←
NIL, mouseButton: Menus.MouseButton ← red, shift, control:
BOOL ←
FALSE]
--Menus.MenuProc-- = {
ENABLE UNWIND => NULL;
theWord: ROPE ← NIL;
correction: ROPE ← NARROW[clientData];
wordCount: INT ← 0;
s: Selection;
Locker:
INTERNAL
PROC [root: TiogaOpsDefs.Ref] =
TRUSTED {
ExtractOneWord:
INTERNAL
PROC [word:
REF
TEXT]
RETURNS [stop:
BOOLEAN ←
FALSE] =
TRUSTED {
stop ← wordCount > 0;
IF wordCount = 0
THEN
theWord ← Rope.FromRefText[word];
wordCount ← wordCount + 1;
};
s ← ProcessSelection[FALSE, TRUE, TRUE].s;
[] ← MapWordsInSelection[s.start, s.end, ExtractOneWord, TRUE];
IF theWord =
NIL
THEN
IO.PutF[permMessage, "No word has been selected.\n"];
};
MakeCorrection:
INTERNAL
PROC [root: TiogaOpsDefs.Ref] = {
looks: ROPE ← TiogaOps.FetchLooks[s.start.node, s.start.where];
TiogaOps.SaveForPaste[];
TiogaOps.Delete[];
TiogaOps.SetLooks[looks];
TiogaOps.InsertRope[correction];
s.end.where ← s.start.where + correction.Size[] - 1;
TiogaOps.SetSelection[viewer: s.viewer, start: s.start, end: s.end, pendingDelete: TRUE];
};
CorrectTiogaOpsCallWithLocks[Locker ! TiogaOps.NoSelection => {
IO.PutF[permMessage, "No word is selected, so no correction can be applied.\n"];
GOTO leave;}];
IF wordCount = 0
THEN {
IO.PutF[permMessage, "No word is selected, so no correction can be applied.\n"];
RETURN;
};
IF wordCount > 1
THEN {
IO.PutF[permMessage, "More than one word selected, so no correction can be applied.\n"];
RETURN;
};
IF ~(Rope.Equal[theWord, lastWordCorrected]
OR Rope.Equal[theWord, theWordCorrected])
THEN {
IO.PutF[permMessage, "Selected word, \"%g\", is not the same as \"%g\".\n", , IO.rope[theWord], IO.rope[theWordCorrected]];
RETURN;
};
totalCorrectionsApplied ← totalCorrectionsApplied + 1;
IF ~Rope.Equal[theWord, correction]
THEN
CorrectTiogaOpsCallWithLocks[MakeCorrection ! TiogaOps.NoSelection => {
IO.PutF[permMessage, "There is no selection, so no correction can be applied.\n"];
GOTO leave;}];
lastWordCorrected ← correction;
IF mouseButton = blue
THEN {
s ← ProcessSelection[FALSE,FALSE, TRUE].s;
s.start.node ← s.end.node;
s.start.where ← s.end.where + 1;
--IF GotBitTable[] THEN-- {
totalCheckCommands ← totalCheckCommands + 1;
SpellCheckWithin[s, TRUE, TRUE, TRUE];
};
};
};
ResetButton:
ENTRY Menus.MenuProc = {
ENABLE UNWIND => NULL;
permMessage.PutRope["Freeing bit tables ... "];
FlushCache[];
permMessage.PutRope[" ... ensuring insertions written ... "];
WaitTillClean[];
permMessage.PutRope[" ... done resetting.\n"];
};
NoCorrectionButton:
ENTRY Menus.MenuProc = {
ENABLE UNWIND => NULL;
IO.PutF[permMessage, "\"%g\" may be misspelled; no similar words are in the list.\n", IO.rope[theWordCorrected]];
};
container: Viewer ← NIL;
registration: ViewerEvents.EventRegistration;
QuitSpellTool:
ENTRY ViewerEvents.EventProc = {
ENABLE UNWIND => NULL;
IF viewer = container
AND event = destroy
THEN {
SpellingToolDefinition.FinalizeDictionaryServer[];
SaveInsertions[];
toolExists ← FALSE;
container ← NIL;
ViewerEvents.UnRegisterEventProc[registration, destroy];
};
};
DefineMenu:
PROC = {
Menus.AppendMenuEntry
[line: 0, menu: toolMenu, entry: Menus.CreateEntry[name: "Reset", proc: ResetButton, guarded: TRUE]];
Menus.AppendMenuEntry
[line: 0, menu: toolMenu, entry: Menus.CreateEntry[name: "Definition", proc: SpellingToolDefinition.DefinitionButton, 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: "Insert in:", proc: InsertHead]];
};
NewSpellingTool:
ENTRY
PROC
RETURNS [new:
BOOL] = {
ENABLE UNWIND => NULL;
ymax: INTEGER ← 0;
emWidth: INTEGER ← VFonts.CharWidth['M];
emHeight: INTEGER ← VFonts.FontHeight[];
ts: TypeScript.TS;
IF toolExists THEN RETURN [FALSE];
toolExists ← new ← TRUE;
container ← Containers.Create[[name: "Spelling Tool", iconic: TRUE, column: right, menu: toolMenu, scrollable: FALSE, icon: spellingToolIcon]];
registration ← ViewerEvents.RegisterEventProc[QuitSpellTool, destroy];
ts ← TypeScript.Create[info: [parent: container, border: FALSE, scrollable: TRUE, wy: 2], paint: FALSE];
[permMessageIn, permMessage] ← ViewerIO.CreateViewerStreams[name: "Definitions", viewer: ts, editedStream: FALSE];
TRUSTED {
Process.Detach[FORK SpellingToolDefinition.InitializeDictionaryServer[permMessage]];
};
ViewerOps.SetOpenHeight[container, 6*emHeight + 8];
Containers.ChildXBound[container, ts];
Containers.ChildYBound[container, ts];
ViewerOps.PaintViewer[container, all];
};
WaitTillClean:
INTERNAL
PROC = {
WHILE dirty
DO
BROADCAST CleanlinessWanted;
WAIT CleanlinessAchieved;
ENDLOOP;
};
InitializeProfile:
ENTRY UserProfile.ProfileChangedProc = {
ENABLE UNWIND => NULL;
oldBitTableFilename: ROPE ← BitTableFilename;
oldGlobalAuxilliaryWordlistFilenames: RopeList ← globalAuxilliaryWordlistFilenames;
globalAuxilliaryWordlistFilenames ← UserProfile.ListOfTokens["SpellingTool.Wordlists", LIST["SpellingTool.Wordlist"]];
FOR rl: RopeList ← globalAuxilliaryWordlistFilenames, rl.rest
WHILE rl #
NIL
DO
rl.first ← FS.ExpandName[name: rl.first, wDir: installationDirectory].fullFName;
ENDLOOP;
BitTableFilename ←
FS.ExpandName[
name: UserProfile.Token[
key: "SpellingTool.BitTable",
default: "SpellingTool.BitTable"],
wDir: installationDirectory
].fullFName;
insertionInterval ← UserProfile.Number["SpellingTool.InsertionInterval", 60];
pluralStripping ← UserProfile.Boolean["SpellingTool.PluralStripping", TRUE];
IF
NOT
(oldBitTableFilename.Equal[BitTableFilename, FALSE]
AND ListEqual[
oldGlobalAuxilliaryWordlistFilenames,
globalAuxilliaryWordlistFilenames])
THEN FlushCache[];
BROADCAST CleanlinessWanted;
};
SpellingToolCommand: Commander.CommandProc = {
IF NewSpellingTool[]
THEN {
UserProfile.CallWhenProfileChanges[InitializeProfile];
TRUSTED {Process.Detach[FORK MakeAdditionsPermanent[]];};
};
};
TRUSTED {Process.SetTimeout[@CleanlinessAchieved, Process.SecondsToTicks[30]]};
DefineMenu[];
Commander.Register[
key: "SpellingTool",
proc: SpellingToolCommand,
doc: "Makes a Spelling Tool."];
END.
CHANGE LOG
Created by Nix on February 25, 1985 5:38:53 pm PST, END
Spreitzer, February 25, 1985 5:38:07 pm PST
Added VM operations to accommodate Beach's addition of explicit VM management.
changes to: DIRECTORY, SpellingToolImpl, SpellCheckWithin
Spreitzer, February 27, 1985 11:42:53 am PST
Introduced auxilliary wordlist file lists local to document.
changes to: , DIRECTORY
Edited on February 27, 1985 8:46:37 pm PST, by Spreitzer
This entry from NewStuff, rather than EditorComforts
changes to: viewerLocalDataKey, LocalDataRep, FileDataRep, localDataCache, fileDatas, dirty, toolMenu, SpellCheckWithin, WordCollector (local of Locker, local of InsertWordOrInsertWordAndSpell), Locker (local of InsertWordOrInsertWordAndSpell), InsertWordOrInsertWordAndSpell, Valid, ValidCache (local of GetLocalData), UseIt (local of GetLocalData), ComputeLocalData, NoteNonUse, Unenter, FlushCache, FilterLocalDataList, GetBitFile, AddWord, SaveInsertions, NILList (local of SaveInsertions), ApplyInsertionButton, NewSpellingTool, FlushCacheButton, NoCorrectionButton, QuitSpellTool, DIRECTORY, STREAM, Viewer, ViewerList, FindFileData, FilterViewerList, ProposeCorrections, container, generalDestroyRegistration, NoteDestruction, Commander, GetLocalData, MaybeAddWordlistInserter, NewSpellingTool, SpellCheckWithin, DIRECTORY, toolMenu, NewSpellingTool, NewSpellingTool, TRUSTED, DefineMenu, FlushCache, ResetButton, InitializeProfile, SpellingToolImpl, localAuxilliaryWordlistFilenamesProperty, ProposeCorrections, Locker (local of ApplyCorrectionButton), ApplyCorrectionButton
Edited on March 2, 1985 1:44:16 pm PST, by Spreitzer
Changed default source of bit table from [Indigo]<SpellingToolData>SpellingTool.BitTable to SpellingTool.BitTable
changes to: InitializeProfile