SourceFileOpsViewersImpl.mesa
Copyright Ó 1989, 1990, 1991, 1992 by Xerox Corporation. All rights reserved.
Linda Howe October 16, 1989 11:28:13 am PDT
Coolidge, July 31, 1990 6:23 pm PDT
Christian Jacobi, August 25, 1990 0:30 am PDT
Last tweaked by Mike Spreitzer on April 9, 1992 8:09 am PDT
Some basic feedback operations on source files. This is the impl to use when Viewers is available.
Philip James, February 14, 1992 9:34 am PST
DIRECTORY
BackStop USING [Call],
BasicTime USING [GMT, nullGMT],
Convert,
IO USING [PutRope, PutFR, PutFR1, STREAM],
LineNumberExtras,
PFS USING [Error, UniqueID, nullUniqueID, PathFromRope, RopeFromPath, FileInfo, GetWDir],
PFSNames USING [PATH, Component, Cat, IsAbsolute, EmptyPath, ShortName, ComponentRope],
Rope USING [Cat, Concat, Equal, Find, Length, ROPE],
SourceFileOps,
SourceFileOpsExtras,
SystemNames USING [LocalDir],
TEditDocument USING [SelectionId],
TEditSelectionOps USING [ShowGivenPositionRange],
TextNode USING [Ref],
TiogaLies,
TiogaMenuOps USING [Open],
TiogaOps USING [GetProp, GetSelection, Location, LocOffset, Ref, Root, ViewerDoc, WhichSelection],
ViewerClasses USING [Column, Viewer],
ViewerOps USING [EnumerateViewers, OpenIcon],
ViewerPrivate USING [rootViewerTree],
ViewerTools USING [GetSelectionContents];
SourceFileOpsViewersImpl: CEDAR MONITOR
IMPORTS BackStop, Convert, IO, LineNumberExtras, PFS, PFSNames, Rope, SystemNames, TEditSelectionOps, TiogaLies, TiogaMenuOps, TiogaOps, ViewerOps, ViewerPrivate, ViewerTools
EXPORTS SourceFileOps, SourceFileOpsExtras
= BEGIN OPEN SourceFileOpsExtras;
Useful types
FullPosition: TYPE ~ SourceFileOpsExtras.Position;
ShortPosition: TYPE ~ SourceFileOps.Position;
PATH: TYPE ~ PFSNames.PATH;
STREAM: TYPE = IO.STREAM;
Viewer: TYPE = ViewerClasses.Viewer;
Severity: TYPE = {success, comment, warning, fatal};
success => operation is completed
comment => intermediate information
warning => something is wrong, but not fatal
fatal => operation is completed, but did not succeed
ReportProc: TYPE = PROC [msg: ROPE, severity: Severity];
... is the type of user-supplied procedure used to report results in above operations
EXPORTS to CirioViewerOps
mySelectionToTiogaOpsSelection: ARRAY SourceFileOps.WhichSelection OF TiogaOps.WhichSelection = [primary, secondary, feedback];
mySelectionToTEditSelectionOpsSelection: ARRAY SourceFileOps.WhichSelection OF TEditDocument.SelectionId = [primary, secondary, feedback];
Expand: PROC [s: ShortPosition] RETURNS [FullPosition] ~ {
RETURN [[s.fileName, nullUniqueID, s.index]]};
Restrict: PROC [f: FullPosition] RETURNS [ShortPosition] ~ {
RETURN [[f.fileName, f.index]]};
GetSelection: PUBLIC PROC [selection: SourceFileOps.WhichSelection ← primary] RETURNS [ShortPosition]
= {RETURN Restrict[FullGetSelection[selection].pos]};
FullGetSelection: PUBLIC PROC [selection: SourceFileOps.WhichSelection ← primary] RETURNS [pos: FullPosition, contents: ROPE] = {
viewer: Viewer;
start, end: TiogaOps.Location;
[viewer: viewer, start: start, end: end] ← TiogaOps.GetSelection[mySelectionToTiogaOpsSelection[selection]];
contents ← ViewerTools.GetSelectionContents[];
IF viewer # NIL AND NOT viewer.destroyed AND NOT viewer.newFile THEN {
root: TiogaOps.Ref ← TiogaOps.Root[start.node];
fileName: FileName ← PFS.PathFromRope[viewer.file];
charStart, charEnd: INT;
createUnique: UniqueID;
lines: LineNumberExtras.LineRange;
lineRange: Range;
createVal: REF ANY ← TiogaOps.GetProp[root, $FileCreateDate];
IF createVal#NIL THEN {
rgmt: REF BasicTime.GMT;
TRUSTED {rgmt ← LOOPHOLE[createVal]}; --YUK!
createUnique ← [[rgmt^]]}
ELSE createUnique ← nullUniqueID;
IF fileName=PFSNames.EmptyPath THEN fileName ← NIL;
charStart ← TiogaOps.LocOffset[loc1: [root, 0], loc2: start, skipCommentNodes: TRUE];
charEnd ← TiogaOps.LocOffset[loc1: [root, 0], loc2: end, skipCommentNodes: TRUE];
lines ← LineNumberExtras.PositionsToLines[TiogaLies.OpsRefToTextNodeRef[root], [charStart, charEnd], TRUE];
lineRange ← [MIN[lines.first, Index.LAST], MIN[lines.last, Index.LAST]];
IF start = end THEN {charEnd ← noIndex; lineRange.last ← noIndex};
SetHint[viewer];
RETURN [[fileName, createUnique, [char: [charStart, charEnd], line: lineRange]], contents]};
RETURN [noPosition, contents]};
OpenSource: PUBLIC PROC [desc: ROPE, pos: ShortPosition, feedBack: STREAMNIL, selection: WhichSelection ← feedback]
= {FullOpenSource[desc, Expand[pos], feedBack, selection]};
FullOpenSource: PUBLIC PROC [desc: ROPE, pos: FullPosition, feedBack: STREAMNIL, selection: WhichSelection ← feedback] = {
v: Viewer ← NIL;
report: ReportProc--PROC [msg: ROPE, severity: Severity]-- = {
IF feedBack # NIL THEN {feedBack.PutRope[msg]; feedBack.PutRope["\N"]};
RETURN};
inner: PROC = TRUSTED {
ok ← NameToOpenViewer[pos, report, TRUE, selection];
RETURN};
ok: BOOLTRUE;
err: ROPENIL;
err ← BackStop.Call[inner];
IF err#NIL THEN Report[report, fatal, " Can't open: ", err]
ELSE IF ok THEN Report[report, success, " Source opened", FullFmtIdxs[pos], "."];
};
FormatPosition: PUBLIC PROC [pos: ShortPosition] RETURNS [Rope.ROPE]
~ {RETURN FullFormatPosition[Expand[pos]]};
FullFormatPosition: PUBLIC PROC [pos: FullPosition] RETURNS [Rope.ROPE] ~ {
name: ROPE ← "a broken file name";
time: ROPE ← "a broken time";
IF pos.fileName#NIL AND pos.fileName#PFSNames.EmptyPath
THEN name ← PFS.RopeFromPath[pos.fileName !PFS.Error => CONTINUE]
ELSE name ← "an unnamed file";
IF pos.uniqueID.egmt.time=BasicTime.nullGMT
THEN time ← "unspecified time"
ELSE time ← Convert.RopeFromTime[pos.uniqueID.egmt.time, years, seconds, FALSE, FALSE !Convert.Error => CONTINUE];
IF pos.uniqueID.egmt.usecs#0 THEN time ← IO.PutFR["%g + %gus", [rope[time]], [cardinal[pos.uniqueID.egmt.usecs]] ];
IF pos.uniqueID.host#[0, 0] THEN time ← IO.PutFR["%g host [%xH, %xH]", [rope[time]], [cardinal[pos.uniqueID.host.a]], [cardinal[pos.uniqueID.host.b]] ];
RETURN name.Cat[" created at ", time, " ", FullFmtIdxs[pos]]};
FmtIdxs: PUBLIC PROC [pos: ShortPosition] RETURNS [ROPE]
~ {RETURN FullFmtIdxs[Expand[pos]]};
FullFmtIdxs: PUBLIC PROC [pos: FullPosition] RETURNS [ROPE] ~ {
SELECT TRUE FROM
pos.index = ALL[noRange] => RETURN [" (no position)"];
pos.index[line] = noRange => RETURN IO.PutFR1[" (%g)", [rope[FmtRange[pos.index[char], "char ", "chars "]]]];
pos.index[char] = noRange => RETURN IO.PutFR1[" (%g)", [rope[FmtRange[pos.index[line], "line ", "line "]]]];
ENDCASE => RETURN IO.PutFR[" (%g = %g)", [rope[FmtRange[pos.index[char], "char ", "chars "]]], [rope[FmtRange[pos.index[line], "line ", "line "]]]]};
FmtRange: PUBLIC PROC [r: Range, introSingular, introPlural: ROPENIL] RETURNS [ROPE] ~ {
IF r = noRange THEN RETURN introPlural.Concat["unspecified"];
IF r.last = noIndex THEN RETURN introSingular.Concat[Convert.RopeFromInt[r.first]];
RETURN IO.PutFR["%g%g..%g", [rope[introPlural]], [integer[r.first]], [integer[r.last]] ]};
Report: PROC [report: ReportProc, severity: Severity, r1,r2,r3,r4: ROPENIL] = {
IF report # NIL THEN {
msg: ROPE ← Rope.Cat[r1, r2, r3, r4];
report[msg, severity];
};
};
ColumnLocked: PROC [column: ViewerClasses.Column] RETURNS [locked: BOOLFALSE] ~ {
viewer: ViewerClasses.Viewer ← ViewerPrivate.rootViewerTree[column];
IF viewer = NIL THEN RETURN[FALSE];
DO
IF viewer.lock.count > 0 THEN RETURN[TRUE];
IF (viewer ← viewer.sibling) = NIL THEN EXIT;
ENDLOOP;
};
NameToOpenViewer: PROC [pos: FullPosition, report: ReportProc, ignoreOnesBeingChanged: BOOL, selection: WhichSelection] RETURNS [BOOL] = {
name: PATH ~ pos.fileName;
IF name # NIL AND name # PFSNames.EmptyPath THEN {
short: PFSNames.Component ← name.ShortName;
nameRope: ROPEPFS.RopeFromPath[name];
viewer: Viewer ← NIL;
viewer ← FindViewer[
fileName: short.ComponentRope, created: pos.uniqueID.egmt.time, ignoreOnesBeingChanged: ignoreOnesBeingChanged];
IF viewer = NIL OR viewer.destroyed THEN TRUSTED {
column: ViewerClasses.Column ← IF ColumnLocked[left] THEN right ELSE left;
IF ColumnLocked[column] THEN {
viewer ← NIL; Report[report, warning, " Due to a locked column, ", nameRope, " wasn't opened."]; }
ELSE {
Report[report, comment, " Opening ", nameRope, "... "];
viewer ← TiogaMenuOps.Open[nameRope,, column];
};
};
IF viewer#NIL AND viewer.iconic THEN
IF ColumnLocked[viewer.column] THEN
viewer ← NIL
ELSE
ViewerOps.OpenIcon[viewer];
SELECT TRUE FROM
viewer=NIL => {
Report[report, warning, Rope.Concat[
" couldn't open; would have highlighted ",
FullFmtIdxs[pos]]];
RETURN [FALSE]};
pos.index[char]#noRange => TEditSelectionOps.ShowGivenPositionRange[viewer, mySelectionToTEditSelectionOpsSelection[selection], pos.index[char].first, MAX[pos.index[char].first+3, pos.index[char].last], TRUE, FALSE];
pos.index[line]#noRange => LineNumberExtras.ToSelectionRange[viewer, [pos.index[line].first, IF pos.index[line].last=noIndex THEN pos.index[line].first ELSE pos.index[line].last], TRUE];
ENDCASE => NULL;
RETURN [TRUE]};
RETURN [FALSE]};
FindViewer: PROC [fileName: Rope.ROPE, created: BasicTime.GMT, 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 v.class.flavor # $Text THEN RETURN [TRUE];
Ignore non-text viewers
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 create date
IF created # BasicTime.nullGMT THEN {
root: TiogaOps.Ref ← TiogaOps.ViewerDoc[v];
createVal: REF ANY ← TiogaOps.GetProp[root, $FileCreateDate];
IF createVal#NIL THEN {
rgmt: REF BasicTime.GMT;
TRUSTED {rgmt ← LOOPHOLE[createVal]}; --YUK! BasicTime.GMT is opaque
IF rgmt^ # created THEN RETURN [TRUE];
}
ELSE RETURN [TRUE];
};
viewer ← v;
RETURN [FALSE];
};
};
viewer ← GetHint[fileName];
IF viewer # NIL AND NOT visit[viewer] THEN RETURN;
viewer ← NIL;
ViewerOps.EnumerateViewers[visit];
RETURN};
GetHint: 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.
viewer: Viewer ← hintViewer;
SELECT TRUE FROM
viewer = NIL => {};
viewer.destroyed => {};
Rope.Equal[name, viewer.file, FALSE] => RETURN [viewer];
ENDCASE;
RETURN [NIL];
};
SetHint: PROC [viewer: Viewer] = {
hintViewer ← viewer
};
hintViewer: Viewer ← NIL;
systemDir: ROPE ~ PFS.RopeFromPath[PFS.GetWDir[]];
commandsDir: ROPE ~ SystemNames.LocalDir["Commands"];
pathPrefixes: LIST OF ROPELIST[NIL, commandsDir, systemDir];
first try the current default for the running process
... then try the Commands subdirectory (stuff from Environment.df)
... then try the default system directory (stuff from BootEssentials.df)
FullFileName: PROC [shortPath: PATH, uniqueID: PFS.UniqueID ← PFS.nullUniqueID] RETURNS [fullPath: PATHNIL] = {
returns NIL if not found
wPath: PATH;
IF shortPath.IsAbsolute THEN RETURN [shortPath];
FOR paths: LIST OF ROPE ← pathPrefixes, paths.rest UNTIL paths = NIL DO
ENABLE PFS.Error => IF error.group # bug THEN LOOP;
wPath ← PFS.PathFromRope[paths.first];
fullPath ← shortPath.Cat[wPath];
checkThis: BOOL ← NeedsCheck[createTime, fullName];
RETURN [PFS.FileInfo[name: fullPath, wantedUniqueID: uniqueID
! PFS.Error => IF error.group # bug THEN LOOP
].fullFName];
ENDLOOP;
};
NeedsCheck: PROC [createTime: BasicTime.GMT, fullName: ROPE] RETURNS [BOOL] = {
No need to check unless date is unbound and the file is remote. If we change our policy on this here is the place to change it.
RETURN [createTime = BasicTime.nullGMT AND NOT Rope.Match["[]*", fullName]];
};
END.