AMViewerOpsImpl.mesa
Copyright © 1985, 1986 by Xerox Corporation. All rights reserved.
Russ Atkinson (RRA) June 11, 1986 5:56:51 pm PDT
DIRECTORY
AMBridge USING [ContextPC],
AMFiles USING [FullFileName],
AMModel USING [Context, ContextSection, RootContext, Section, SectionSource, Source, SourceFileName, SourceObj, SourceSection, SourceVersion],
AMModelBridge USING [LoadedSection, LoadedSectionForProc, LoadedSectionForProgPC],
AMViewerOps USING [ReportProc, Severity],
AMTypes USING [GlobalParent, TV, TVType, UnderClass],
BackStop USING [Call],
BasicTime USING [GMT, nullGMT, ToNSTime, FromNSTime],
BcdDefs USING [NullVersion, VersionStamp],
FileViewerOps USING [SelectionOption],
FS USING [Error, FileInfo],
IO USING [PutRope, PutFR, PutFR1, STREAM],
Rope USING [Cat, Concat, Equal, Fetch, Find, Flatten, Length, Match, ROPE, Size, Text],
TEditDocument USING [LineTable, TEditDocumentData],
TEditSelectionOps USING [ShowGivenPosition],
TEditTouchup USING [LockAfterRefresh, UnlockAfterRefresh],
TextNode USING [Forward, Location, Ref],
TiogaMenuOps USING [Open],
TiogaOps USING [GetSelection, Location, LocOffset, Ref, Root],
VersionMapDefaults USING [FileNameFromVersion],
ViewerClasses USING [Viewer],
ViewerEvents USING [EventProc, EventRegistration, RegisterEventProc, UnRegisterEventProc],
ViewerOps USING [EnumerateViewers, OpenIcon],
WorldVM USING [LocalWorld, World];
AMViewerOpsImpl: CEDAR MONITOR
IMPORTS AMBridge, AMFiles, AMModel, AMModelBridge, AMTypes, BackStop, BasicTime, FS, IO, Rope, TEditSelectionOps, TEditTouchup, TextNode, TiogaMenuOps, TiogaOps, VersionMapDefaults, ViewerEvents, ViewerOps, WorldVM
EXPORTS AMViewerOps, FileViewerOps
= BEGIN OPEN AMViewerOps, FileViewerOps;
Useful types
Context: TYPE = AMModel.Context;
Section: TYPE = AMModel.Section;
Source: TYPE = AMModel.Source;
ROPE: TYPE = Rope.ROPE;
TV: TYPE = AMTypes.TV;
VersionStamp: TYPE = BcdDefs.VersionStamp;
NullVersion: VersionStamp = BcdDefs.NullVersion;
Viewer: TYPE = ViewerClasses.Viewer;
World: TYPE = WorldVM.World;
EXPORTS to AMViewerOps
ViewerFromSection: PUBLIC PROC [section: Section, report: ReportProc] RETURNS [viewer: Viewer ← NIL] = TRUSTED {
errmsg: ROPENIL;
inner: PROC = TRUSTED {
sourceVersion: VersionStamp;
source: Source ← AMModel.SectionSource[section];
name: ROPE ← AMModel.SourceFileName[source];
start: INT ← 0;
IF name = NIL THEN {
errmsg ← "can't get source name";
RETURN};
sourceVersion ← AMModel.SourceVersion[source];
IF sourceVersion = NullVersion THEN {
errmsg ← "can't get source version stamp";
RETURN};
name ← SourceToFullName[source, report];
IF name = NIL THEN {
errmsg ← VersionExpectedMessage[sourceVersion];
RETURN};
errmsg ← "can't set selection";
WITH s: source^ SELECT FROM
field => start ← s.firstCharIndex;
ENDCASE;
viewer ← NameToOpenViewer[name, sourceVersion, report, TRUE, start];
IF viewer = NIL THEN RETURN;
errmsg ← NIL;
};
msg: ROPE ← BackStop.Call[inner];
IF errmsg = NIL THEN errmsg ← msg;
IF errmsg # NIL THEN report[errmsg, fatal];
};
SourceFromSelection: PUBLIC PROC [which: SelectionOption ← primary] RETURNS [fileName: ROPENIL, index: INT ← -1] = {
... returns the selected viewer's name and the source index into the viewer. Returns [NIL, -1] if the selection is not in a valid Tioga viewer.
viewer: Viewer ← NIL;
start: TiogaOps.Location;
[viewer: viewer, start: start] ←
TiogaOps.GetSelection[IF which = primary THEN primary ELSE feedback];
IF viewer # NIL AND NOT viewer.destroyed AND NOT viewer.newFile THEN {
root: TiogaOps.Ref ← TiogaOps.Root[start.node];
offset: INT
TiogaOps.LocOffset[loc1: [root, 0], loc2: start, skipCommentNodes: TRUE];
index ← offset;
fileName ← viewer.file;
SetHint[viewer];
};
};
SectionFromSelection: PUBLIC PROC [world: World ← NIL, which: SelectionOption ← primary] RETURNS [section: Section ← NIL, contexts: LIST OF Context ← NIL] = {
... returns a location for the given selection (primary or feedback); returns NIL if can't do it. If world = NIL then world ← LocalWorld. warn = TRUE => the version of the source file for the given location does not correspond to the viewer (although this is no guarantee that the wrong thing happened).
name: ROPENIL;
index: INT ← -1;
[name, index] ← SourceFromSelection[which];
[section, contexts] ← SectionFromSource[world, name, index];
};
SectionFromSource: PUBLIC PROC [world: World ← NIL, name: ROPENIL, index: INT ← 0] RETURNS [section: Section ← NIL, contexts: LIST OF Context ← NIL] = TRUSTED {
shortName: ROPE ← StripDir[name];
IF Rope.Length[shortName] # 0 THEN {
sourceVersion: VersionStamp ← FileVersion[name].version;
source: Source ← NIL;
context: Context =
AMModel.RootContext[IF world = NIL THEN WorldVM.LocalWorld[] ELSE world];
IF index <= 0
THEN
source ← NEW[AMModel.SourceObj ← [
fileName: shortName,
class: prog,
versionStamp: sourceVersion,
sourceRange: entire[]]]
ELSE
source ← NEW[AMModel.SourceObj ← [
fileName: shortName,
class: statement,
versionStamp: sourceVersion,
sourceRange: field[index, index]]];
[section, contexts] ← AMModel.SourceSection[source, context];
};
};
FileVersion: PROC [name: ROPE, desiredVersion: VersionStamp ← NullVersion] RETURNS [fullName: ROPENIL, version: VersionStamp ← NullVersion] = {
returns NullVersion if any errors occur (like missing file)
gmt: BasicTime.GMT
= IF desiredVersion = NullVersion
THEN BasicTime.nullGMT
ELSE BasicTime.FromNSTime[desiredVersion.time];
fullName ← AMFiles.FullFileName[name, gmt];
IF fullName.Length[] = 0 THEN RETURN;
version.time ← BasicTime.ToNSTime[
FS.FileInfo[name: fullName, wantedCreatedTime: gmt ! FS.Error => CONTINUE].created
];
};
SourceFromTV: PUBLIC PROC [tv: TV, report: ReportProc] RETURNS [name: ROPENIL, index: INT ← -1] = {
gets the source file name and the source index for the given TV, which must be a local frame or global frame, if not successful, then name = NIL & index < 0
errmsg: ROPENIL;
inner: PROC = TRUSTED {
section: Section ← NIL;
source: Source ← NIL;
SELECT AMTypes.UnderClass[AMTypes.TVType[tv]] FROM
procedure =>
section ← AMModelBridge.LoadedSectionForProc[tv].section;
globalFrame =>
section ← AMModel.ContextSection[tv];
localFrame => {
section ← AMModelBridge.LoadedSectionForProgPC[
prog: AMTypes.GlobalParent[tv],
pc: AMBridge.ContextPC[tv]].section;
};
ENDCASE => {errmsg ← "invalid TV"; RETURN};
source ← AMModel.SectionSource[section];
name ← SourceToFullName[source, report];
IF name = NIL THEN {
errmsg ← VersionExpectedMessage[AMModel.SourceVersion[source]];
RETURN};
WITH s: source^ SELECT FROM
entire => index ← 0;
field => index ← s.firstCharIndex;
ENDCASE => ERROR;
};
err: ROPENIL;
Report[report, comment, " Finding source... "];
err ← BackStop.Call[inner];
IF errmsg = NIL THEN errmsg ← err;
IF errmsg # NIL
THEN Report[report, fatal, " No source: ", errmsg]
ELSE Report[report, success];
};
OpenSource: PUBLIC PROC [fileName: ROPE, index: INT, chars: INT ← 2, feedBack: IO.STREAMNIL] = {
uses the results of GetSource to open a viewer on the source
if index >= 0, then also sets the selection to the given index (for chars characters)
viewer: Viewer ← NIL;
report: ReportProc--PROC [msg: ROPE, severity: Severity]-- = {
IF feedBack # NIL THEN feedBack.PutRope[msg];
};
inner: PROC = TRUSTED {
viewer ← NameToOpenViewer[fileName, NullVersion, report, TRUE, index];
};
err: ROPENIL;
err ← BackStop.Call[inner];
IF err # NIL
THEN Report[report, fatal, " Can't open: ", err]
ELSE Report[report, success, IF index # 0 THEN IO.PutFR1[" Source opened (position: %g).", [integer[index]]] ELSE " Source opened."];
};
Report: PROC [report: ReportProc, severity: Severity, r1,r2,r3,r4: ROPENIL] = {
msg: ROPE ← Rope.Cat[r1, r2, r3, r4];
report[msg, severity];
};
OnScreen: PROC [viewer: Viewer, point: TextNode.Location] RETURNS [BOOL] = {
OnScreen determines whether or not the given location is visible for the given viewer.
IF viewer = NIL OR point.node = NIL THEN RETURN [FALSE];
IF viewer.destroyed OR viewer.iconic THEN RETURN [FALSE];
WITH viewer.data SELECT FROM
tdd: TEditDocument.TEditDocumentData => {
Now we know that we have a Tioga document
IF TEditTouchup.LockAfterRefresh[tdd, "OnScreen"] THEN {
At this point tdd is really and truly locked up. We must release it at the end of the block or bad things will happen.
ENABLE {UNWIND => TEditTouchup.UnlockAfterRefresh[tdd]};
lines: TEditDocument.LineTable ← tdd.lineTable;
found: BOOLFALSE;
IF lines # NIL AND lines.lastLine >= 4 THEN {
first: TextNode.Location ← lines[1].pos;
last: TextNode.Location ← lines[lines.lastLine-1].pos;
each: TextNode.Ref ← first.node;
IF point.node = first.node AND point.where < first.where THEN GO TO quickOut;
IF point.node = last.node AND point.where > last.where THEN GO TO quickOut;
WHILE each # NIL DO
IF each = point.node THEN {found ← TRUE; EXIT};
IF each = last.node THEN EXIT;
each ← TextNode.Forward[each].nx;
ENDLOOP;
EXITS quickOut => {};
};
TEditTouchup.UnlockAfterRefresh[tdd];
RETURN [found];
};
};
ENDCASE;
RETURN [FALSE];
};
NameToOpenViewer: PROC [name: ROPE, version: VersionStamp, report: ReportProc, ignoreOnesBeingChanged: BOOL, index: INT] RETURNS [viewer: Viewer ← NIL] = {
IF Rope.Length[name] # 0 THEN {
short: ROPENIL;
IF version = NullVersion THEN version ← FileVersion[name].version;
short ← StripDir[name];
viewer ← FindViewer[
fileName: short, version: version, ignoreOnesBeingChanged: ignoreOnesBeingChanged];
IF viewer = NIL OR viewer.destroyed THEN TRUSTED {
Report[report, comment, " Opening ", name, "... "];
viewer ← TiogaMenuOps.Open[IO.PutFR["%g|%g", [rope[name]], [integer[index]] ]];
RETURN;
};
IF viewer.iconic THEN ViewerOps.OpenIcon[viewer];
TEditSelectionOps.ShowGivenPosition[viewer, index];
};
};
VersionExpectedMessage: PROC [version: VersionStamp] RETURNS [ROPE] = {
RETURN[IO.PutFR1["source of %g expected", [time[BasicTime.FromNSTime[version.time]]]]];
};
SourceToFullName: PROC [source: Source, report: ReportProc] RETURNS [ROPE] = TRUSTED {
returns NIL if the file is not available in the right source
fileVersion, sourceVersion: VersionStamp ← NullVersion;
name: ROPE ← AMModel.SourceFileName[source];
IF NOT Rope.Match["*.mesa*", name, FALSE] THEN name ← name.Concat[".mesa"];
sourceVersion ← AMModel.SourceVersion[source];
[name, fileVersion] ← FileVersion[name, sourceVersion];
IF fileVersion = sourceVersion THEN RETURN [name];
Report[report, comment, " (version map) "];
RETURN [VersionMapDefaults.FileNameFromVersion[$Source, sourceVersion].name];
};
StripDir: PROC [name: ROPE] RETURNS [Rope.Text] = {
... turns a long path name into a short path name, stripping off the version information and the directory, but preserving the extension.
size,pos: INT ← Rope.Size[name];
start: INT ← 0;
DO
IF pos = 0 THEN {pos ← size; EXIT};
pos ← pos - 1;
SELECT Rope.Fetch[name, pos] FROM
'! => EXIT;
'. => {pos ← size; EXIT};
ENDCASE;
ENDLOOP;
start ← pos;
WHILE start > 0 DO
SELECT Rope.Fetch[name, start ← start - 1] FROM
'/, '\\, '[, '], '<, '> => {
start ← start + 1;
EXIT};
ENDCASE;
ENDLOOP;
RETURN [Rope.Flatten[name, start, pos-start]];
};
FindViewer: PROC [fileName: Rope.ROPE, version: VersionStamp, ignoreOnesBeingChanged: BOOL] RETURNS [viewer: Viewer ← NIL] = {
visit: SAFE PROC [v: Viewer] RETURNS [BOOLTRUE] = TRUSTED {
-- return TRUE to continue, FALSE to stop
vname: Rope.ROPE ← v.file;
pos: INT;
someSaveInProgress: PROC RETURNS[ans: BOOLFALSE] = CHECKED {
vv: Viewer ← v;
IF vv.saveInProgress THEN RETURN[TRUE];
WHILE (vv ← vv.link) # NIL AND (vv # v) DO
IF vv.saveInProgress THEN RETURN[TRUE];
ENDLOOP;
};
IF ignoreOnesBeingChanged AND (v.newVersion OR someSaveInProgress[])
THEN RETURN [TRUE];
don't find viewers with changed contents or being saved
IF vname = NIL THEN vname ← v.name;
just in case the file name is not present
IF Rope.Length[vname] = 0 THEN RETURN [FALSE];
pos ← Rope.Find[vname, fileName, 0, FALSE];
IF pos >= 0 THEN {
Same short name, so check the version stamp
IF version # NullVersion THEN {
fileVersion: VersionStamp = FileVersion[vname, version].version;
IF fileVersion # version THEN RETURN [TRUE];
};
viewer ← v;
RETURN [FALSE];
};
};
viewer ← GetHint[fileName];
IF viewer # NIL AND NOT visit[viewer] THEN RETURN;
viewer ← NIL;
ViewerOps.EnumerateViewers[visit];
};
GetHint: ENTRY PROC [name: ROPE] RETURNS [Viewer ← NIL] = {
We keep a hint which indicates the last viewer we used to make a selection. If that viewer is no longer valid (NIL or destroyed), or if it does not have the same short file name as the one we are seeking, then we return NIL. Otherwise the hint has saved us some valuable time, and we are more likely to find the selected viewer if we have multiple viewers or split viewers for the same file.
SELECT TRUE FROM
hintViewer = NIL => {};
hintViewer.destroyed => hintViewer ← NIL;
Rope.Equal[name, hintName, FALSE] => RETURN [hintViewer];
ENDCASE;
RETURN [NIL];
};
SetHint: ENTRY PROC [viewer: Viewer] = {
IF hintRegistration # NIL THEN {
ViewerEvents.UnRegisterEventProc[hintRegistration, destroy];
hintRegistration ← NIL;
};
IF viewer = NIL THEN hintName ← NIL ELSE hintName ← viewer.file;
hintViewer ← viewer;
IF viewer # NIL THEN {
hintRegistration ← ViewerEvents.RegisterEventProc[FlushHint, destroy, viewer, TRUE];
};
};
FlushHint: ViewerEvents.EventProc = {
SetHint[NIL];
};
hintName: ROPENIL;
hintViewer: Viewer ← NIL;
hintRegistration: ViewerEvents.EventRegistration ← NIL;
END.