DefDWIMImpl.mesa
Mike Spreitzer March 1, 1987 5:45:39 pm PST
Last tweaked by Mike Spreitzer on November 30, 1987 2:48:46 pm PST
DIRECTORY Atom, BackStop, Basics, DefDWIM, DocCache, FileDWIM, FS, IO, JaMAndStyleAnalyses, Menus, MesaAnalyses, MessageWindow, RefTab, RegularExpression, Rope, RopeHash, RopeList, RopeReader, SymTab, TDJaMScanner, TDLexing, TEditDocument, TEditDocumentPrivate, TEditInput, TEditOps, TEditProfile, TEditRefresh, TEditSelection, TEditSelectionOps, TextFind, TextNode, TiogaMenuOps, TiogaOps, TiogaRopes, TiogaStreams, TreeFind, UserProfile, ViewerClasses, ViewerForkers, VM;
DefDWIMImpl: CEDAR MONITOR
INVARIANT
The Ring.
IMPORTS Atom, BackStop, DocCache, FileDWIM, FS, IO, JaMAndStyleAnalyses, MesaAnalyses, MessageWindow, RefTab, RegularExpression, Rope, RopeHash, RopeList, RopeReader, SymTab, TDJaMScanner, TDLexing, TEditDocumentPrivate, TEditInput, TEditOps, TEditProfile, TEditRefresh, TEditSelection, TextFind, TextNode, TiogaMenuOps, TiogaOps, TiogaRopes, TiogaStreams, TreeFind, UserProfile, ViewerForkers, VM
EXPORTS DefDWIM
SHARES ViewerClasses --in order to change existing Def button, which doesn't decode control
=
BEGIN OPEN TDLexing, DefDWIM;
Stuff
LORA: TYPE = LIST OF REF ANY;
ROPEList: TYPE = LIST OF ROPE;
RefTextNode: TYPE = TextNode.RefTextNode;
MesaAnalysis: TYPE = MesaAnalyses.MesaAnalysis;
Viewer: TYPE = ViewerClasses.Viewer;
Kernel
RegistrationList: TYPE = LIST OF Registration;
Registration: TYPE = RECORD [Test: LanguageTest, Find: Finder];
Failure: PUBLIC ERROR [where: Place, why: ROPE] = CODE;
registry: RegistrationList ← NIL;
FindDef: PUBLIC PROC [subjectRope: ROPE, from: Place, deep, verbose: BOOL, interrupt: REF BOOLNIL] RETURNS [fp: FoundPlace] --Finder-- = {
FOR rl: RegistrationList ← registry, rl.rest WHILE rl # NIL DO
IF rl.first.Test[from] THEN RETURN [rl.first.Find[subjectRope, from, deep, verbose, interrupt]];
ENDLOOP;
RETURN [DefaultFinder[subjectRope, from, deep, verbose, interrupt]];
};
Register: PUBLIC PROC [languageTest: LanguageTest, finder: Finder] = {
registry ← CONS[[languageTest, finder], registry];
};
RopeSeq: TYPE = REF RopeSequence;
RopeSequence: TYPE = RECORD [elts: SEQUENCE length: NAT OF ROPE];
msgRing: RopeSeq ← CreateRopeSeq[100];
msgRingInsert: NAT ← 0;
SetRingSize: ENTRY PROC [size: NAT] = {
ENABLE UNWIND => NULL;
msgRing ← CreateRopeSeq[size];
msgRingInsert ← 0;
};
Msg: ENTRY PROC [clearFirst: BOOL, r1, r2, r3, r4, r5: ROPENIL] = {
ENABLE UNWIND => msgRingInsert ← 0;
msg: ROPE = Rope.Cat[r1, r2, r3, r4, r5];
MessageWindow.Append[msg, clearFirst];
IF clearFirst THEN {
msgRingInsert ← msgRingInsert + 1;
IF msgRingInsert = msgRing.length THEN msgRingInsert ← 0;
msgRing[msgRingInsert] ← msg;
}
ELSE msgRing[msgRingInsert] ← msgRing[msgRingInsert].Concat[msg];
};
CreateRopeSeq: PROC [length: NAT] RETURNS [rs: RopeSeq] = {
rs ← NEW [RopeSequence[length]]};
GetMsgs: ENTRY PROC [maxLen: NAT ← 32] RETURNS [history: ROPEList] = {
ENABLE UNWIND => NULL;
len: NAT = MIN[msgRing.length, maxLen];
i: NAT ← msgRingInsert;
history ← NIL;
THROUGH [1 .. len] DO
history ← CONS[msgRing[i], history];
i ← (i+msgRing.length-1) MOD msgRing.length;
ENDLOOP;
history ← history;
};
Some particular languages
DefaultFinder: PROC [subjectRope: ROPE, from: Place, deep, verbose: BOOL, interrupt: REF BOOLNIL] RETURNS [fp: FoundPlace] --Finder-- = {
quick: TextFind.Finder ~ TextFind.CreateFromRope[pattern: Rope.Cat[subjectRope, ":"], literal: TRUE, word: TRUE];
textRoot: RefTextNode = TextNode.Root[from.loc.node];
start: INT = 0;
startLoc: Location = TextNode.LocRelative[[textRoot, 0], start];
before, after: INT;
foundLoc, nameEnd: Location;
foundNode: TextNode.Ref ← NIL;
TryFwd: PROC [fromLoc: Location] RETURNS [found: BOOL] = {
[found: found, where: foundNode, before: before, after: after] ← TreeFind.Try[finder: quick, first: fromLoc.node, start: fromLoc.where, interrupt: interrupt];
};
TryBkwd: PROC RETURNS [found: BOOL] = {
[found: found, where: foundNode, before: before, after: after] ← TreeFind.TryBackwards[finder: quick, first: from.loc.node, len: from.loc.where, last: startLoc.node, lastStart: startLoc.where, interrupt: interrupt];
};
found: BOOLSELECT from.searchOrder FROM
fromStart => TryFwd[[textRoot, 0]],
fwdThenBkwd => TryFwd[from.loc] OR TryBkwd[],
bkwdThenFwd => TryBkwd[] OR TryFwd[from.loc],
ENDCASE => ERROR;
IF interrupt#NIL AND interrupt^ THEN Failure[from, Rope.Concat["interrupted search for ", subjectRope]];
IF NOT found THEN {
dot: INT ~ subjectRope.FindBackward["."];
subjLen: INT ~ subjectRope.Length;
IF dot IN (0 .. subjLen-1) THEN {
module: ROPE ~ subjectRope.Substr[len: dot];
sub: ROPE ~ subjectRope.Substr[start: dot+1];
next: Place ~ EnterCedarModule[from, module, verbose];
fp ← FindCedarDef[sub, next, deep, verbose, FALSE, NIL, MakeStack[], interrupt];
RETURN};
};
IF found THEN {
foundLoc ← TextNode.LocRelative[[foundNode, 0], before];
nameEnd ← TextNode.LocRelative[foundLoc, after-2 - before];
after ← TextNode.LocOffset[[textRoot, 0], [foundNode, after]];
fp ← [[from.fileName, foundLoc, fwdThenBkwd], nameEnd];
}
ELSE Failure[from, Rope.Cat["Couldn't find ", subjectRope]];
};
RecognizeJS: PROC [place: Place] RETURNS [is: BOOL] --LanguageTest-- = {
is ← WhatJSType[place] # Other;
};
JSType: TYPE = {JaM, Style, Other};
WhatJSType: PROC [place: Place] RETURNS [type: JSType] = {
cp: FS.ComponentPositions;
fullFName, ext: ROPE;
IF place.fileName.Length[] = 0 THEN RETURN [Other];
[fullFName, cp, ] ← FS.ExpandName[place.fileName];
ext ← fullFName.Substr[cp.ext.start, cp.ext.length];
type ← SELECT TRUE FROM
ext.Equal["JaM", FALSE] => JaM,
ext.Equal["Style", FALSE] => Style,
ENDCASE => Other;
};
JSFinder: PROC [subjectRope: ROPE, from: Place, deep, verbose: BOOL, interrupt: REF BOOLNIL] RETURNS [fp: FoundPlace] --Finder-- = {
type: JSType = WhatJSType[from];
IF deep THEN subjectRope ← Rope.Cat["look.", subjectRope];
fp ← FindJSDef[subjectRope, from, verbose, NIL, MakeStack[], interrupt];
};
FindJSDef: PROC [subjectRope: ROPE, from: Place, verbose: BOOL, searchedOpens: SymTab.Ref--file name b $T--, stack: RefTab.Ref--Frame b $T--, interrupt: REF BOOLNIL] RETURNS [fp: FoundPlace] --Finder-- = {
frame: Frame = NEW [FramePrivate ← [subjectRope, from]];
IF stack.Fetch[frame].found THEN {
Failure[from, Rope.Concat["Recursion detected for ", subjectRope]];
};
IF NOT stack.Insert[frame, $T] THEN ERROR;
IF verbose THEN Msg[TRUE, "Looking for ", subjectRope, " in ", from.fileName];
{ENABLE UNWIND => [] ← stack.Delete[frame];
quick: RegularExpression.Finder = RegularExpression.CreateFromRope[pattern: Rope.Cat["(", subjectRope, ")"], literal: TRUE, word: TRUE];
textRoot: RefTextNode = TextNode.Root[from.loc.node];
anal: JaMAndStyleAnalyses.JSAnalysis = JaMAndStyleAnalyses.GetAnalysis[textRoot, from.fileName, verbose];
start: INT = IF anal # NIL THEN anal.bodyStart ELSE 0;
startLoc: Location = TextNode.LocRelative[[textRoot, 0], start];
here: INT = TextNode.LocOffset[[textRoot, 0], from.loc];
before, after: INT;
foundLoc, nameEnd: Location;
TryFwd: PROC [searchFrom: INT] RETURNS [found: BOOL] = {
searchRope: ROPE = MF[TiogaRopes.RopeFromTioga[textRoot]];
found ← FALSE;
WHILE NOT found DO
[found: found, before: before, after: after] ← RegularExpression.SearchRope[finder: quick, rope: searchRope, start: searchFrom, interrupt: interrupt];
IF NOT found THEN EXIT;
IF NOT JaMOKAround[searchRope, subjectRope, before, after] THEN {searchFrom ← after; found ← FALSE};
ENDLOOP;
};
TryBkwd: PROC RETURNS [found: BOOL] = {
searchRope: ROPE = MF[TiogaRopes.RopeFromTioga[textRoot]];
searchLength: INT ← here - start;
found ← FALSE;
WHILE NOT found DO
[found: found, before: before, after: after] ← RegularExpression.SearchRopeBackwards[finder: quick, rope: searchRope, start: start, len: searchLength, interrupt: interrupt];
IF NOT found THEN EXIT;
IF NOT JaMOKAround[searchRope, subjectRope, before, after] THEN {searchLength ← before - start; found ← FALSE};
ENDLOOP;
};
found: BOOLSELECT from.searchOrder FROM
fromStart => TryFwd[0],
fwdThenBkwd => TryFwd[here] OR TryBkwd[],
bkwdThenFwd => TryBkwd[] OR TryFwd[here],
ENDCASE => ERROR;
IF interrupt#NIL AND interrupt^ THEN Failure[from, Rope.Concat["interrupted search for ", subjectRope]];
IF found THEN {
foundLoc ← TextNode.LocRelative[[textRoot, 0], before+1];
nameEnd ← TextNode.LocRelative[foundLoc, after-2 - (before+1)];
after ← TextNode.LocOffset[[textRoot, 0], nameEnd]+1;
fp ← [[from.fileName, foundLoc, fwdThenBkwd], nameEnd];
}
ELSE {
Try: PROC [files: ROPEList, ext: ROPE] RETURNS [found: BOOL] = {
FOR files ← files, files.rest WHILE files # NIL DO
fileName: ROPE = files.first.Concat[ext];
IF interrupt#NIL AND interrupt^ THEN Failure[from, Rope.Concat["interrupted search for ", subjectRope]];
IF searchedOpens = NIL THEN searchedOpens ← SymTab.Create[case: TRUE];
found ← TRUE;
IF searchedOpens.Fetch[fileName].found THEN found ← FALSE
ELSE {
IF NOT searchedOpens.Insert[fileName, $T] THEN ERROR;
fp ← FindJSDef[subjectRope, JSModuleBody[from, fileName, verbose, stack, interrupt].where, verbose, searchedOpens, stack, interrupt !Failure => {found ← FALSE; CONTINUE}];
};
IF found THEN RETURN;
ENDLOOP;
found ← FALSE;
};
IF anal # NIL AND (Try[anal.runs, ".JaM"] OR Try[anal.attachs, ".Style"])
THEN NULL
ELSE Failure[from, Rope.Cat["Couldn't find ", subjectRope]];
};
};
IF NOT stack.Delete[frame] THEN ERROR;
};
JSModuleBody: PROC [from: Place, filename: ROPE, verbose: BOOL, stack: RefTab.Ref--Frame b $T--, interrupt: REF BOOL] RETURNS [fp: FoundPlace] = {
ans: FileDWIM.Answer = FileDWIM.ResolveHint[filename, from.fileName, FALSE];
IF ans.fullFileName = NIL THEN ERROR Failure[from, Rope.Cat["couldn't find file ", filename]];
{next: Place = [ans.fullFileName, [GetDoc[from, ans.fullFileName], 0], fromStart];
fp ← [next, next.loc];
}};
JaMOKAround: PROC [searchRope, target: ROPE, before, after: INT] RETURNS [ok: BOOL] = {
Token: TYPE = TDJaMScanner.Token;
reader: RopeReader.Ref = RopeReader.Create[];
Match: PROC [left: {leftSquare, leftCurly}] RETURNS [ok: BOOL] = {
DO
token: Token = TDJaMScanner.GetToken[reader];
SELECT token.type FROM
nil => RETURN [FALSE--because EOF--];
name => {
a: ATOM = NARROW[TDJaMScanner.ParseToken[token, searchRope]];
SELECT a FROM
leftSquare => IF NOT Match[leftSquare] THEN RETURN [FALSE];
rightSquare => RETURN [left = leftSquare];
ENDCASE => NULL;
};
string, int, real => NULL;
lbrace => IF NOT Match[leftCurly] THEN RETURN [FALSE];
rbrace => RETURN [left = leftCurly];
comment => NULL;
ENDCASE => ERROR;
ENDLOOP;
};
stack: LORALIST[target];
IF after >= searchRope.Length[] THEN RETURN [FALSE];
reader.SetPosition[searchRope, after];
DO
token: Token = TDJaMScanner.GetToken[reader];
SELECT token.type FROM
nil => RETURN [FALSE--because EOF--];
name => {
a: ATOM = NARROW[TDJaMScanner.ParseToken[token, searchRope]];
SELECT a FROM
dotCvx, $bp => NULL;
dotDef, $StyleParam => RETURN [stack # NIL AND stack.rest # NIL AND stack.rest.rest = NIL];
slashDef, $StyleRule, $ScreenRule, $PrintRule => RETURN [stack # NIL AND stack.rest # NIL AND stack.rest.rest # NIL AND stack.rest.rest.rest = NIL];
slashXdef => RETURN [stack # NIL AND stack.rest # NIL AND stack.rest.rest # NIL AND stack.rest.rest.rest = NIL];
leftSquare => IF Match[leftSquare] THEN stack ← CONS[$square, stack] ELSE RETURN [FALSE];
dotTrue => stack ← CONS[a, stack];
ENDCASE => RETURN [FALSE];
};
string, int, real => stack ← CONS[TDJaMScanner.ParseToken[token, searchRope], stack];
lbrace => IF Match[leftCurly] THEN stack ← CONS[$curly, stack] ELSE RETURN [FALSE];
rbrace => RETURN [FALSE];
comment => NULL;
ENDCASE => ERROR;
ENDLOOP;
};
dotDef: ATOM = Atom.MakeAtom[".def"];
dotCvx: ATOM = Atom.MakeAtom[".cvx"];
dotTrue: ATOM = Atom.MakeAtom[".true"];
slashDef: ATOM = Atom.MakeAtom["/def"];
slashXdef: ATOM = Atom.MakeAtom["/xdef"];
leftSquare: ATOM = Atom.MakeAtom["["];
rightSquare: ATOM = Atom.MakeAtom["]"];
RecognizeLisp: PROC [place: Place] RETURNS [is: BOOL] = {
textRoot: RefTextNode = TextNode.Root[place.loc.node];
asRope: ROPE = MF[TiogaRopes.RopeFromTioga[node: textRoot, skipCommentNode: TRUE]];
length: INT = asRope.Length[];
in: IO.STREAM = IO.RIS[asRope];
[] ← in.SkipWhitespace[];
IF in.EndOf[] THEN RETURN [FALSE];
{firstNonwhite: CHAR = in.GetChar[];
in.Close[];
IF firstNonwhite # '( THEN RETURN [FALSE];
FOR i: INT ← length-1, i-1 WHILE i > 0 DO
SELECT asRope.Fetch[i] FROM
IN ['\000 .. IO.SP] => NULL;
') => RETURN [TRUE];
ENDCASE => RETURN [FALSE];
ENDLOOP;
RETURN [FALSE];
}};
LispFinder: PROC [subjectRope: ROPE, from: Place, deep, verbose: BOOL, interrupt: REF BOOLNIL] RETURNS [fp: FoundPlace] --Finder-- = {
quick: TextFind.Finder ← TextFind.CreateFromRope[pattern: Rope.Cat["(defun ", subjectRope], literal: TRUE, word: TRUE];
textRoot: RefTextNode = TextNode.Root[from.loc.node];
start: INT = 0;
startLoc: Location = TextNode.LocRelative[[textRoot, 0], start];
before, after: INT;
foundLoc, nameEnd: Location;
foundNode: TextNode.Ref ← NIL;
TryFwd: PROC [fromLoc: Location] RETURNS [found: BOOL] = {
[found: found, where: foundNode, before: before, after: after] ← TreeFind.Try[finder: quick, first: from.loc.node, start: from.loc.where, interrupt: interrupt];
};
TryBkwd: PROC RETURNS [found: BOOL] = {
[found: found, where: foundNode, before: before, after: after] ← TreeFind.TryBackwards[finder: quick, first: from.loc.node, len: from.loc.where, last: startLoc.node, lastStart: startLoc.where, interrupt: interrupt];
};
found: BOOLSELECT from.searchOrder FROM
fromStart => TryFwd[startLoc],
fwdThenBkwd => TryFwd[from.loc] OR TryBkwd[],
bkwdThenFwd => TryBkwd[] OR TryFwd[from.loc],
ENDCASE => ERROR;
IF interrupt#NIL AND interrupt^ THEN Failure[from, Rope.Concat["interrupted search for ", subjectRope]];
IF found THEN {
foundLoc ← TextNode.LocRelative[[foundNode, 0], before+7];
nameEnd ← TextNode.LocRelative[foundLoc, after-1 - (before+7)];
after ← TextNode.LocOffset[[textRoot, 0], [foundNode, after]];
fp ← [[from.fileName, foundLoc, fwdThenBkwd], nameEnd];
}
ELSE Failure[from, Rope.Cat["Couldn't find ", subjectRope]];
};
CedarTree: TYPE = REF ANY --actually UNION [FieldSelection, ROPE]--;
FieldSelection: TYPE = REF FieldSelectionPrivate;
FieldSelectionPrivate: TYPE = RECORD [
record: CedarTree,
field: ROPE
];
Frame: TYPE = REF FramePrivate;
FramePrivate: TYPE = RECORD [tree: CedarTree, from: Place];
guessDIRECTORY: BOOLTRUE;
RecognizeCedar: PROC [place: Place] RETURNS [isCedar: BOOL] --LanguageTest-- = {
cp: FS.ComponentPositions;
fullFName, ext: ROPE;
IF place.fileName.Length[] = 0 THEN RETURN [FALSE];
[fullFName, cp, ] ← FS.ExpandName[place.fileName];
ext ← fullFName.Substr[cp.ext.start, cp.ext.length];
isCedar ← ext.Equal["mesa", FALSE] OR ext.Equal["cedar", FALSE];
};
CedarFinder: PROC [subjectRope: ROPE, from: Place, deep, verbose: BOOL, interrupt: REF BOOLNIL] RETURNS [fp: FoundPlace] --Finder-- = {
in: IO.STREAM = IO.RIS[subjectRope];
p: Lexer = MakeLexer[in];
{ENABLE IO.Error => IF stream = in THEN Failure[noplace, IF ec = SyntaxError THEN "selection not of form ID(\".\"ID)*" ELSE "IO.Error parsing selection"];
t1: Token = GetToken[p];
IF t1.kind # tokenID THEN Failure[noplace, "selection not of form ID(\".\"ID)*"];
{tree: CedarTree = GetTree[p, t1];
in.Close[];
IF p.tokenStack = NIL OR p.tokenStack.rest # NIL OR p.tokenStack.first.kind # tokenEOF THEN Failure[noplace, "selection not of form ID(\".\"ID)*"];
fp ← FindCedarDef[tree, from, deep, verbose, TRUE, NIL, MakeStack[], interrupt];
}}};
commentControl: TreeFind.CommentControl[includeComments..excludeComments] ← excludeComments;
Controls whether the Cedar finder ignores comment nodes.
FindCedarDef: PROC [tree: CedarTree, from: Place, deep, verbose, leftmost: BOOL, searchedOpens: SymTab.Ref--module name b $T--, stack: RefTab.Ref--Frame b $T--, interrupt: REF BOOLNIL] RETURNS [fp: FoundPlace] = {
If not leftmost, we never guess it might be a missing DIRECTORY entry.
An OPENed module without a USING list is recorded in searchedOpens, which may be NIL.
frame: Frame = NEW [FramePrivate ← [tree, from]];
IF stack.Fetch[frame].found THEN {
Failure[from, Rope.Concat["Recursion detected for ", FmtTree[tree]]];
};
IF NOT stack.Insert[frame, $T] THEN ERROR;
{ENABLE UNWIND => [] ← stack.Delete[frame];
WITH tree SELECT FROM
fs: FieldSelection => {
next: Place = FindCedarDef[fs.record, from, TRUE, verbose, leftmost, searchedOpens, stack, interrupt].where;
fp ← FindCedarDef[fs.field, next, deep, verbose, FALSE, NIL, stack, interrupt];
};
target: ROPE => {
quick: TextFind.Finder ← TextFind.CreateFromRope[pattern: Rope.Cat[target, ":"], literal: TRUE, word: TRUE];
textRoot: RefTextNode = TextNode.Root[from.loc.node];
T2RIndex: PROC [count: INT] RETURNS [x: INT] ~ {x ← IF commentControl=excludeComments THEN TextNode.LocOffset[[textRoot, 0], TextNode.LocRelative[[textRoot, 0], count], 1, TRUE] ELSE count};
R2TIndex: PROC [count: INT] RETURNS [y: INT] ~ {y ← IF commentControl=excludeComments THEN TextNode.LocOffset[[textRoot, 0], TextNode.LocRelative[[textRoot, 0], count, 1, TRUE]] ELSE count};
ma: MesaAnalysis = MesaAnalyses.GetAnalysis[textRoot, from.fileName, verbose];
start: INT = IF ma # NIL THEN ma.bodyStart ELSE 0;
startLoc: Location = TextNode.LocRelative[[textRoot, 0], start];
before, after: INT;
found: BOOL;
foundLoc, nameEnd: Location;
IF verbose THEN Msg[TRUE, "Looking for ", target, " in ", from.fileName];
{foundNode: TextNode.Ref ← NIL;
TryFwd: PROC [fromLoc: Location] RETURNS [found: BOOL] = {
[found: found, where: foundNode, before: before, after: after] ← TreeFind.Try[finder: quick, first: from.loc.node, start: from.loc.where, commentControl: commentControl, interrupt: interrupt];
};
TryBkwd: PROC RETURNS [found: BOOL] = {
[found: found, where: foundNode, before: before, after: after] ← TreeFind.TryBackwards[finder: quick, first: from.loc.node, len: from.loc.where, last: startLoc.node, lastStart: startLoc.where, commentControl: commentControl, interrupt: interrupt];
};
found ← SELECT from.searchOrder FROM
fromStart => TryFwd[[textRoot, 0]],
fwdThenBkwd => TryFwd[from.loc] OR TryBkwd[],
bkwdThenFwd => TryBkwd[] OR TryFwd[from.loc],
ENDCASE => ERROR;
IF interrupt#NIL AND interrupt^ THEN Failure[from, Rope.Concat["interrupted search for ", target]];
IF verbose THEN Msg[FALSE, " ."];
IF found THEN {
foundLoc ← TextNode.LocRelative[[foundNode, 0], before];
nameEnd ← TextNode.LocRelative[foundLoc, after-2 - before];
after ← TextNode.LocOffset[[textRoot, 0], [foundNode, after], 1, commentControl=excludeComments];
}
};
IF (NOT found) AND ma # NIL THEN {
def: REF ANY = ma.globalDefs.Fetch[target].val;
IF def # NIL THEN {
fp ← WITH def SELECT FROM
it: MesaAnalyses.InterfaceType => CedarModuleBody[from, it, verbose, stack, interrupt],
ir: MesaAnalyses.InterfaceRecord => IF deep
THEN CedarModuleBody[from, ir.type, verbose, stack, interrupt]
ELSE [[from.fileName, TextNode.LocRelative[[textRoot, 0], ir.namePos.before], fwdThenBkwd], TextNode.LocRelative[[textRoot, 0], ir.namePos.after-1]],
ie: MesaAnalyses.InterfaceElement => FindCedarDef[ie.name, CedarModuleBody[from, ie.interface.type, verbose, stack, interrupt].where, deep, verbose, FALSE, searchedOpens, stack, interrupt],
ENDCASE => ERROR;
GOTO Dun};
};
IF NOT found THEN {
here: INT = TextNode.LocOffset[[textRoot, 0], from.loc];
targLen: INT = target.Length[];
slow: RegularExpression.Finder = RegularExpression.CreateFromRope[target.Concat["[ \n\t]**(',[ \n\t]**[a..zA..Z][a..zA..Z0..9]**[ \n\t]**)**('([~'(')]++')[ \n\t]**|)':"]];
xStart: INT ~ T2RIndex[start];
xHere: INT ~ T2RIndex[here];
searchRope: ROPE ~ MF[TiogaRopes.RopeFromTioga[node: textRoot, skipCommentNode: commentControl=excludeComments]];
TryFwd: PROC [searchFrom: INT] RETURNS [found: BOOL] = {
found ← FALSE;
WHILE NOT found DO
[found: found, before: before, after: after] ← RegularExpression.SearchRope[finder: slow, rope: searchRope, start: searchFrom, interrupt: interrupt];
IF NOT found THEN EXIT;
IF NOT CedarOKBefore[searchRope, before] THEN {searchFrom ← after; found ← FALSE};
ENDLOOP;
};
TryBkwd: PROC RETURNS [found: BOOL] = {
searchLength: INT ← xHere - xStart;
found ← FALSE;
WHILE NOT found DO
[found: found, before: before, after: after] ← RegularExpression.SearchRopeBackwards[finder: slow, rope: searchRope, start: xStart, len: searchLength, interrupt: interrupt];
IF NOT found THEN EXIT;
IF NOT CedarOKBefore[searchRope, before] THEN {searchLength ← before - xStart; found ← FALSE};
ENDLOOP;
};
IF verbose THEN Msg[TRUE, "Looking hard for ", target, " in ", from.fileName];
found ← SELECT from.searchOrder FROM
fromStart => TryFwd[0],
fwdThenBkwd => TryFwd[xHere] OR TryBkwd[],
bkwdThenFwd => TryBkwd[] OR TryFwd[xHere],
ENDCASE => ERROR;
IF interrupt#NIL AND interrupt^ THEN Failure[from, Rope.Concat["interrupted search for ", target]];
IF verbose THEN Msg[FALSE, " ."];
IF found THEN {
foundLoc ← TextNode.LocRelative[[textRoot, 0], before, 1, commentControl=excludeComments];
nameEnd ← TextNode.LocRelative[foundLoc, targLen-1];
};
};
IF (NOT found) AND ma # NIL THEN {
FOR owus: MesaAnalyses.IRList ← ma.openedWithoutUsingList, owus.rest WHILE owus # NIL DO
ir: MesaAnalyses.InterfaceRecord = owus.first;
IF interrupt#NIL AND interrupt^ THEN Failure[from, Rope.Concat["interrupted search for ", target]];
IF searchedOpens = NIL THEN searchedOpens ← SymTab.Create[case: TRUE];
found ← TRUE;
IF searchedOpens.Fetch[ir.type.typeClass].found THEN found ← FALSE
ELSE {
IF NOT searchedOpens.Insert[ir.type.typeClass, $T] THEN ERROR;
fp ← FindCedarDef[target, CedarModuleBody[from, ir.type, verbose, stack, interrupt].where, deep, verbose, FALSE, searchedOpens, stack, interrupt !Failure => {found ← FALSE; CONTINUE}];
};
IF found THEN GOTO Dun;
ENDLOOP;
};
IF (NOT found) AND guessDIRECTORY AND leftmost THEN {
IF verbose THEN Msg[TRUE, "Guessing ", target, " missing from DIRECTORY in ", from.fileName];
{ans: FileDWIM.Answer = FileDWIM.ResolveHint[target.Concat[".mesa"], from.fileName, FALSE];
IF ans.fullFileName # NIL THEN {
next: Place = [ans.fullFileName, [GetDoc[from, ans.fullFileName], 0], fromStart];
found ← TRUE;
fp ← FindCedarDef[target, next, deep, verbose, FALSE, searchedOpens, stack, interrupt !Failure => {found ← FALSE; CONTINUE}];
IF found THEN GOTO Dun;
};
IF verbose THEN Msg[FALSE, " ."];
}};
IF NOT found THEN ERROR Failure[from, Rope.Cat["couldn't find ", target]];
fp ← [[from.fileName, foundLoc, fwdThenBkwd], nameEnd];
IF NOT deep THEN GOTO Dun;
{in: IO.STREAM = TiogaStreams.CreateInput[
from: textRoot,
commentHandling: SELECT commentControl FROM
includeComments => useDirectly,
excludeComments => discard,
ENDCASE => ERROR];
in.SetIndex[after];
{ENABLE IO.Error => IF stream=in THEN Failure[[from.fileName, TextNode.LocRelative[[textRoot, 0], after], from.searchOrder], IF ec=SyntaxError THEN "Cedar Syntax Error" ELSE "IO.Error"];
p: Lexer = MakeLexer[in];
toke: Token;
SkipMumble[p];
toke ← GetToken[p];
IF toke.kind = tokenID THEN {
IF NOT ReservedWord[toke.rope] THEN {
defTree: CedarTree = GetTree[p, toke];
IF (WITH defTree SELECT FROM
rope: ROPE => NOT PredefinedType[rope],
ENDCASE => TRUE)
THEN {fp ← FindCedarDef[defTree, fp.where, TRUE, verbose, TRUE, NIL, stack, interrupt]; GOTO Dun};
}
ELSE IF toke.rope.Equal["TYPE"] THEN {
toke2: Token = GetToken[p];
IF toke2.kind = tokenSINGLE AND RopeList.Memb[list: LIST["~", "="], r: toke2.rope] THEN {
SkipMumble[p];
{toke3: Token = GetToken[p];
IF toke3.kind = tokenID AND NOT ReservedWord[toke3.rope] THEN {
defTree: CedarTree = GetTree[p, toke3];
IF (WITH defTree SELECT FROM
rope: ROPE => NOT PredefinedType[rope],
ENDCASE => TRUE)
THEN {fp ← FindCedarDef[defTree, fp.where, deep, verbose, TRUE, NIL, stack, interrupt]; GOTO Dun};
};
}};
};
};
in.Close[];
}}};
ENDCASE => ERROR;
EXITS Dun => fp ← fp;
};
IF NOT stack.Delete[frame] THEN ERROR;
};
BeforeState: TYPE ~ {start, gotWhite, gotN, wantG, wantE, wantB, wantP, wantO, dontWantLetter};
CedarOKBefore: PROC [searchRope: ROPE, before: INT] RETURNS [ok: BOOL] = {
Accepts, in reverse: ((nonAlpha ("BEGIN"|"OPEN") white+) | "{" | "[" | ";" | ",") white*
state: BeforeState ← start;
WHILE before > 0 DO
char: CHAR = searchRope.Fetch[before ← before - 1];
SELECT state FROM
start => SELECT char FROM
' , '\n, '\t => state ← gotWhite;
'[, '{, ';, ', => RETURN [TRUE];
ENDCASE => RETURN [FALSE];
gotWhite => SELECT char FROM
' , '\n, '\t => NULL;
'[, '{, ';, ', => RETURN [TRUE];
'N => state ← gotN;
ENDCASE => RETURN [FALSE];
gotN => SELECT char FROM
'I => state ← wantG;
'E => state ← wantP;
ENDCASE => RETURN [FALSE];
wantG => SELECT char FROM
'G => state ← wantE;
ENDCASE => RETURN [FALSE];
wantE => SELECT char FROM
'E => state ← wantB;
ENDCASE => RETURN [FALSE];
wantB => SELECT char FROM
'B => state ← dontWantLetter;
ENDCASE => RETURN [FALSE];
wantP => SELECT char FROM
'P => state ← wantO;
ENDCASE => RETURN [FALSE];
wantO => SELECT char FROM
'O => state ← dontWantLetter;
ENDCASE => RETURN [FALSE];
dontWantLetter => SELECT char FROM
IN ['a .. 'z], IN ['A .. 'Z], IN ['0 .. '9] => RETURN [FALSE];
ENDCASE => RETURN [TRUE];
ENDCASE => ERROR;
ENDLOOP;
RETURN [TRUE]};
SkipMumble: PROC [p: Lexer] = {
DO
SELECT TRUE FROM
p.NextIs["REF"] => LOOP;
p.NextIs["LONG"] => LOOP;
p.NextIsSeq[LIST["POINTER", "TO"]] => LOOP;
p.NextIs["PRIVATE"] => LOOP;
p.NextIs["PUBLIC"] => LOOP;
p.NextIs["READONLY"] => LOOP;
ENDCASE => NULL;
EXIT;
ENDLOOP;
};
GetTree: PROC [p: Lexer, start: Token] RETURNS [tree: CedarTree] = {
want: {dot, name} ← dot;
tree ← start.rope;
DO
toke: Token = GetToken[p];
SELECT want FROM
dot => {
IF toke.rope.Equal["^"] THEN LOOP
ELSE IF toke.rope.Equal["."] THEN want ← name
ELSE {ReturnToken[p, toke]; EXIT};
};
name => {
IF toke.kind = tokenID AND NOT ReservedWord[toke.rope] THEN {
tree ← NEW [FieldSelectionPrivate ← [tree, toke.rope]];
want ← dot;
}
ELSE {ReturnToken[p, toke]; EXIT};
};
ENDCASE => ERROR;
ENDLOOP;
};
EnterCedarModule: PROC [from: Place, moduleName: ROPE, verbose: BOOL] RETURNS [Place] = {
ans: FileDWIM.Answer = FileDWIM.ResolveHint[moduleName.Concat[".mesa"], from.fileName, FALSE];
IF ans.fullFileName = NIL THEN ERROR Failure[from, Rope.Cat["couldn't find file ", moduleName, ".mesa"]];
RETURN [[ans.fullFileName, [GetDoc[from, ans.fullFileName], 0], fromStart]]};
CedarModuleBody: PROC [from: Place, it: MesaAnalyses.InterfaceType, verbose: BOOL, stack: RefTab.Ref--Frame b $T--, interrupt: REF BOOL] RETURNS [fp: FoundPlace] = {
ans: FileDWIM.Answer = FileDWIM.ResolveHint[it.fileHint, from.fileName, FALSE];
IF ans.fullFileName = NIL THEN ERROR Failure[from, Rope.Cat["couldn't find file ", it.fileHint, " for module ", it.name]];
{next: Place = [ans.fullFileName, [GetDoc[from, ans.fullFileName], 0], fromStart];
fp ← FindCedarDef[it.typeClass, next, FALSE, verbose, FALSE, NIL, stack, interrupt];
}};
MF: PROC [rope: ROPE] RETURNS [fr: ROPE] = INLINE {
fr ← IF teledebug THEN rope.Flatten[] ELSE rope};
teledebug: BOOLFALSE;
MakeStack: PROC RETURNS [stack: RefTab.Ref--Frame b $T--] = {
stack ← RefTab.Create[hash: HashFrame, equal: CompareFrames];
};
HashFrame: PROC [key: REF ANY] RETURNS [hash: CARDINAL] --RefTab.HashProc-- = {
f: Frame = NARROW[key];
hash ← HashTree[f.tree] + RopeHash.FromRope[f.from.fileName] + HashRef[f.from.loc.node] + HashInt[f.from.loc.where];
};
HashTree: PROC [t: CedarTree] RETURNS [hash: CARDINAL] = {
WITH t SELECT FROM
x: ROPE => hash ← RopeHash.FromRope[x];
x: FieldSelection => hash ← HashTree[x.record]*3 + RopeHash.FromRope[x.field];
ENDCASE => ERROR;
};
HashRef: PROC [ref: REF ANY] RETURNS [hash: CARDINAL]
= TRUSTED INLINE {hash ← HashInt[LOOPHOLE[ref]]};
HashInt: PROC [i: INT] RETURNS [hash: CARDINAL]
= INLINE {ln: Basics.LongNumber = [li[i]]; hash ← ln.lo + ln.hi};
CompareFrames: PROC [key1, key2: REF ANY] RETURNS [equal: BOOL] = {
f1: Frame = NARROW[key1];
f2: Frame = NARROW[key2];
equal ← CompareTrees[f1.tree, f2.tree] AND f1.from.fileName.Equal[f2.from.fileName, FALSE] AND f1.from.loc = f2.from.loc;
};
CompareTrees: PROC [t1, t2: CedarTree] RETURNS [equal: BOOL] = {
equal ← WITH t1 SELECT FROM
x: ROPE => WITH t2 SELECT FROM
y: ROPE => x.Equal[y],
y: FieldSelection => FALSE,
ENDCASE => ERROR,
x: FieldSelection => WITH t2 SELECT FROM
y: ROPE => FALSE,
y: FieldSelection => CompareTrees[x.record, y.record] AND x.field.Equal[y.field],
ENDCASE => ERROR,
ENDCASE => ERROR;
};
FmtTree: PROC [t: CedarTree] RETURNS [rope: ROPE] = {
rope ← WITH t SELECT FROM
x: ROPE => x,
x: FieldSelection => FmtTree[x.record].Cat[".", x.field],
ENDCASE => ERROR;
};
GetDoc: PROC [from: Place, fileName: ROPE] RETURNS [doc: TextNode.Ref] ~ {
IF fileName=NIL THEN Failure[from, "Trying to fetch NIL-named document"];
IF fileName.Length[]=0 THEN Failure[from, "Trying to fetch empty-named document"];
doc ← DocCache.GetDoc[fileName];
IF doc=NIL THEN Failure[from, IO.PutFR["Bug: DocCache.GetCache[%g] returned NIL", [rope[fileName]]]];
};
User Interface
cmdEnabled: BOOLTRUE;
workVerbose: BOOLTRUE;
DefButt: PROC [parent: REF ANY, clientData: REF ANYNIL,
mouseButton: Menus.MouseButton ← red, shift, control: BOOLFALSE] --Menus.ClickProc-- = {
TEditInput.InterpretAtom[NARROW[parent], defAtoms[mouseButton][control][shift]];
};
defAtoms: ARRAY Menus.MouseButton OF ARRAY --control--BOOL OF ARRAY --shift--BOOL OF ATOM = [
[[$FindNextDef, $FindNextDefCaseless], [$FindNextDefCtl, $FindNextDefCaselessCtl]],
[[$FindAnyDef, $FindAnyDefCaseless], [$FindAnyDefCtl, $FindAnyDefCaselessCtl]],
[[$FindPrevDef, $FindPrevDefCaseless], [$FindPrevDefCtl, $FindPrevDefCaselessCtl]]];
GetImplButt: PROC [parent: REF ANY, clientData: REF ANYNIL,
mouseButton: Menus.MouseButton ← red, shift, control: BOOLFALSE] --Menus.ClickProc-- = {
TEditInput.InterpretAtom[NARROW[parent], implAtoms[mouseButton][control][shift]];
};
implAtoms: ARRAY Menus.MouseButton OF ARRAY --control--BOOL OF ARRAY --shift--BOOL OF ATOM = [
[[$LoadImpl, $LoadSmartImpl], [$LoadImpl, $LoadSmartImpl]],
[[$OpenImpl, $OpenSmartImpl], [$OpenImpl, $OpenSmartImpl]],
[[$CloseAndOpenImpl, $CloseAndOpenSmartImpl], [$CloseAndOpenImpl, $CloseAndOpenSmartImpl]]];
DefCommand: PROC [viewer: Viewer ← NIL] RETURNS [recordAtom: BOOLTRUE, quit: BOOLFALSE] --TiogaOps.CommandProc-- = {[recordAtom, quit] ← DefCommandWork[viewer, FALSE, FALSE, open]};
DefDeepCommand: PROC [viewer: Viewer ← NIL] RETURNS [recordAtom: BOOLTRUE, quit: BOOLFALSE] --TiogaOps.CommandProc-- = {[recordAtom, quit] ← DefCommandWork[viewer, TRUE, FALSE, open]};
LoadSmartImplCommand: PROC [viewer: Viewer ← NIL] RETURNS [recordAtom: BOOLTRUE, quit: BOOLFALSE] --TiogaOps.CommandProc-- = {[recordAtom, quit] ← DefCommandWork[viewer, TRUE, TRUE, load]};
OpenSmartImplCommand: PROC [viewer: Viewer ← NIL] RETURNS [recordAtom: BOOLTRUE, quit: BOOLFALSE] --TiogaOps.CommandProc-- = {[recordAtom, quit] ← DefCommandWork[viewer, TRUE, TRUE, open]};
CloseAndOpenSmartImplCommand: PROC [viewer: Viewer ← NIL] RETURNS [recordAtom: BOOLTRUE, quit: BOOLFALSE] --TiogaOps.CommandProc-- = {[recordAtom, quit] ← DefCommandWork[viewer, TRUE, TRUE, closeAndOpen]};
How: TYPE ~ {load, open, closeAndOpen};
DefCommandWork: PROC [viewer: Viewer, deep, getImpl: BOOL, how: How] RETURNS [recordAtom: BOOLTRUE, quit: BOOLFALSE] --TiogaOps.CommandProc-- = {
subjectRope: ROPE = TEditOps.GetSelContents[];
msg: ROPENIL;
IF NOT cmdEnabled THEN RETURN [FALSE, FALSE];
recordAtom ← FALSE;
quit ← TRUE;
IF subjectRope.IsEmpty[] THEN {
Msg[TRUE, IF getImpl THEN "Select target for impl-search." ELSE "Select target for definition-search."];
MessageWindow.Blink[];
RETURN;
};
{
start: Place;
def: FoundPlace;
inSelf: BOOL;
sought, backErr: ROPENIL;
Protected: PROC ~ {
ENABLE Failure => {
Msg[TRUE, why];
IF where # noplace THEN Msg[FALSE, " in ", where.fileName];
MessageWindow.Blink[];
CONTINUE
};
TEditSelection.CallWithSelAndDocAndTddLocks[viewer, primary, WithLocks];
IF getImpl THEN {
v: Viewer ~ IF inSelf THEN viewer ELSE TEditDocumentPrivate.DoOpenFile[fileName: def.where.fileName, column: right];
subject, fullFName: ROPE;
cp: FS.ComponentPositions;
[fullFName, cp] ← FS.ExpandName[def.where.fileName];
subject ← fullFName.Substr[start: cp.base.start, len: cp.base.length].Cat[".", sought];
SELECT how FROM
load => [] ← TEditDocumentPrivate.DoLoadImplFile[v, subject, FALSE];
open => [] ← TEditDocumentPrivate.DoOpenImplFile[subject, left, v];
closeAndOpen => [] ← TEditDocumentPrivate.DoCloseAndOpenImplFile[v, subject];
ENDCASE => ERROR;
};
MessageWindow.Clear[];
RETURN};
WithLocks: PROC [tdd: TEditDocument.TEditDocumentData, tSel: TEditDocument.Selection] = {
root: TextNode.Ref = IF TextNode.Parent[tdd.text]=NIL THEN tdd.text ELSE TextNode.Root[tdd.text];
startLoc: Location = IF root = TextNode.Root[tSel.start.pos.node] THEN tSel.start.pos ELSE [root, 0];
IF tiogaInterrupt # NIL THEN tiogaInterrupt^ ← FALSE;
start ← [viewer.file, startLoc, bkwdThenFwd];
def ← FindDef[subjectRope, start, deep, workVerbose, tiogaInterrupt];
inSelf ← TextNode.Root[startLoc.node] = TextNode.Root[def.where.loc.node];
IF getImpl THEN {
IF def.where.loc.node # def.nameEnd.node THEN Failure[def.where, "name stretches across node boundary"];
sought ← def.where.loc.node.rope.Substr[start: def.where.loc.where, len: def.nameEnd.where-def.where.loc.where+1];
RETURN;
};
IF inSelf THEN {
tSel.start.pos ← def.where.loc;
tSel.end.pos ← def.nameEnd;
tSel.granularity ← word;
tSel.viewer ← viewer;
tSel.data ← tdd;
tSel.pendingDelete ← FALSE;
TEditOps.RememberCurrentPosition[viewer];
TEditSelection.SetSelLooks[tSel];
TEditSelection.MakeSelection[new: tSel, selection: primary];
TEditInput.CloseEvent[];
TEditRefresh.ScrollToEndOfSel[viewer, FALSE, primary];
}
ELSE {
TEditInput.CloseEvent[];
ViewerForkers.ForkCall[viewer, OpenAndSelect, LIST[NEW [Place ← start], NEW [FoundPlace ← def]]];
};
};
IF protect THEN backErr ← BackStop.Call[Protected] ELSE Protected[];
IF backErr # NIL THEN {MessageWindow.Append[backErr, TRUE]; RETURN [TRUE, FALSE]};
}};
tiogaInterrupt: REF BOOLNIL;
protect: BOOLTRUE;
OpenAndSelect: PROC [data: REF ANY] --ViewerForkers.CallBack-- = {
args: LORA = NARROW[data];
start: REF Place = NARROW[args.first];
end: REF FoundPlace = NARROW[args.rest.first];
first: INT = TextNode.LocNumber[end.where.loc];
last: INT = first + TextNode.LocOffset[end.where.loc, end.nameEnd];
v: Viewer = TEditDocumentPrivate.DoOpenFile[fileName: end.where.fileName, column: right];
IF v = NIL THEN {
Msg[TRUE, "Couldn't open ", end.where.fileName, "."];
MessageWindow.Blink[];
RETURN;
};
ShowGivenPosition[v, first, last, FALSE];
};
ShowGivenPosition: PUBLIC PROC [viewer: Viewer, first, last: INT, skipCommentNodes: BOOLTRUE] = {
Copied from TEditSelectionOpsImpl
DoPosition: PROC [tdd: TEditDocument.TEditDocumentData, tSel: TEditDocument.Selection] = {
tSel.start.pos ← TextNode.LocWithin[tdd.text, first, 1, skipCommentNodes];
tSel.end.pos ← TextNode.LocRelative[tSel.start.pos, last-first, 1, skipCommentNodes];
tSel.granularity ← word;
tSel.viewer ← viewer;
tSel.data ← tdd;
tSel.pendingDelete ← FALSE;
tSel.insertion ← IF TEditProfile.selectionCaret=before THEN before ELSE after;
TEditOps.RememberCurrentPosition[viewer];
TEditSelection.SetSelLooks[tSel];
TEditSelection.MakeSelection[new: tSel, selection: feedback];
TEditRefresh.ScrollToEndOfSel[viewer, FALSE, feedback];
sets bit to cause scroll after refresh
};
TEditSelection.CallWithSelAndDocAndTddLocks[viewer, feedback, DoPosition];
};
AmbushMenuEntry: PROC [first: Menus.MenuEntry, name: ROPE, proc: Menus.ClickProc] = {
me: Menus.MenuEntry;
FOR me ← first, me.link WHILE NOT me.name.Equal[name] DO NULL ENDLOOP;
me.proc ← proc;
};
Setup
NoteProfile: PROC [changeReason: UserProfile.ProfileChangeReason] --UserProfile.ProfileChangedProc-- = {
cmdEnabled ← UserProfile.Boolean["DefDWIM.Enabled", TRUE];
guessDIRECTORY ← UserProfile.Boolean["DefDWIM.GuessDIRECTORY", TRUE];
workVerbose ← UserProfile.Boolean["DefDWIM.Verbose", FALSE];
protect ← UserProfile.Boolean["DefDWIM.Protect", TRUE];
};
GetTiogaInterrupt: PROC RETURNS [interrupt: REF BOOL]
= {interrupt ← TEditInput.interrupt};
Start: PROC = {
tiogaInterrupt ← GetTiogaInterrupt[!VM.AddressFault => CONTINUE];
TiogaOps.RegisterCommand[$FindAnyDef, DefCommand];
TiogaOps.RegisterCommand[$FindAnyDefCtl, DefDeepCommand];
TiogaOps.RegisterCommand[$LoadSmartImpl, LoadSmartImplCommand];
TiogaOps.RegisterCommand[$OpenSmartImpl, OpenSmartImplCommand];
TiogaOps.RegisterCommand[$CloseAndOpenSmartImpl, CloseAndOpenSmartImplCommand];
AmbushMenuEntry[TEditDocumentPrivate.findMenu, "Def", DefButt];
AmbushMenuEntry[TiogaMenuOps.tiogaMenu.lines[0], "GetImpl", GetImplButt];
UserProfile.CallWhenProfileChanges[NoteProfile];
Register[RecognizeLisp, LispFinder];
Register[RecognizeJS, JSFinder];
Register[RecognizeCedar, CedarFinder];
};
Start[];
END.