AMViewerOpsImpl.mesa
Copyright © 1985, 1986 by Xerox Corporation. All rights reserved.
Russ Atkinson (RRA) March 31, 1986 7:54:46 pm PST
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, 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;
ViewerFromSection:
PUBLIC
PROC [section: Section, report: ReportProc]
RETURNS [viewer: Viewer ←
NIL] =
TRUSTED {
errmsg: ROPE ← NIL;
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:
ROPE ←
NIL, 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: ROPE ← NIL;
index: INT ← -1;
[name, index] ← SourceFromSelection[which];
[section, contexts] ← SectionFromSource[world, name, index];
};
SectionFromSource:
PUBLIC
PROC [world: World ←
NIL, name:
ROPE ←
NIL, 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:
ROPE ←
NIL, 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:
ROPE ←
NIL, 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: ROPE ← NIL;
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: ROPE ← NIL;
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.STREAM ←
NIL] = {
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: ROPE ← NIL;
err ← BackStop.Call[inner];
IF err #
NIL
THEN Report[report, fatal, " Can't open: ", err]
ELSE Report[report, success, " Source opened."];
};
Report:
PROC [report: ReportProc, severity: Severity, r1,r2,r3,r4:
ROPE ←
NIL] = {
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: BOOL ← FALSE;
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: ROPE ← NIL;
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.PutFR["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 [
BOOL ←
TRUE] =
TRUSTED {
-- return TRUE to continue, FALSE to stop
vname: Rope.ROPE ← v.file;
pos: INT;
someSaveInProgress:
PROC
RETURNS[ans:
BOOL ←
FALSE] =
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: ROPE ← NIL;
hintViewer: Viewer ← NIL;
hintRegistration: ViewerEvents.EventRegistration ← NIL;