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:
STREAM ←
NIL, selection: WhichSelection ← feedback]
= {FullOpenSource[desc, Expand[pos], feedBack, selection]};
FullOpenSource:
PUBLIC
PROC [desc:
ROPE, pos: FullPosition, feedBack:
STREAM ←
NIL, 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: BOOL ← TRUE;
err: ROPE ← NIL;
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:
ROPE ←
NIL]
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:
ROPE ←
NIL] = {
IF report #
NIL
THEN {
msg: ROPE ← Rope.Cat[r1, r2, r3, r4];
report[msg, severity];
};
};
ColumnLocked:
PROC [column: ViewerClasses.Column]
RETURNS [locked:
BOOL ←
FALSE] ~ {
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: ROPE ← PFS.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 [
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 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
ROPE ←
LIST[
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:
PATH ←
NIL] = {
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]];
};