TEditDocuments2Impl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Plass, April 8, 1985 5:20:37 pm PST
Doug Wyatt, June 20, 1985 3:23:59 pm PDT
Russ Atkinson (RRA) June 25, 1985 2:12:21 am PDT
Michael Plass, March 11, 1986 1:06:03 pm PST
DIRECTORY
AMMiniModel USING [ImplementorName],
BasicTime USING [GMT, nullGMT, Period],
Convert USING [Error, IntFromRope],
FS USING [ComponentPositions, Error, ExpandName, FileInfo, GetDefaultWDir, GetName, nullOpenFile, Open, OpenFile],
Menus USING [Menu, MenuEntry, MenuProc, SetLine],
MessageWindow USING [Append, Blink, Clear],
NodeProps USING [GetProp, NullCopy, NullRead, NullWrite, PutProp, Register],
Rope USING [Concat, Equal, Fetch, Flatten, Find, Match, ROPE, Size, SkipTo, Substr],
TEditDisplay USING [EstablishLine],
TEditDocument USING [maxClip, Selection, SpinAndLock, TEditDocumentData, Unlock],
TEditDocumentPrivate USING [findMenu, FindUnsavedDocument, InitViewerDoc, InitViewerDocInternal, levelMenu, PositionHistory, RecordUnsavedDocument],
TEditInput USING [FreeTree],
TEditInputOps USING [WaitForInsertToFinish],
TEditOps USING [FileNameProc],
TEditProfile USING [DefaultMenuChoice, implExtensions, menu1, menu2, menu3, openFirstLevelOnly, sourceExtensions, tryVersionMap],
TEditSelection USING [FindRope, fSel, LockSel, MakeSelection, pSel, sSel, UnlockSel],
TEditSelectionOps USING [ShowGivenPosition],
TextEdit USING [Size],
TextNode USING [Location, LocNumber, LocWithin, NarrowToTextNode, nullLocation, Ref, RefTextNode, Root],
VersionMap USING [MapList, Range, RangeList, RangeToEntry, ShortNameToRanges],
VersionMapDefaults USING [GetMapList],
ViewerClasses USING [Column, Viewer],
ViewerForkers USING [CallBack, ForkCall, ForkPaint],
ViewerGroupLocks USING [CallRootAndLinksUnderWriteLock],
ViewerLocks USING [CallUnderColumnLocks],
ViewerOps USING [AddProp, CloseViewer, ComputeColumn, CreateViewer, DestroyViewer, EnumerateViewers, EnumProc, EstablishViewerPosition, FetchProp, MoveBelowViewer, OpenIcon, PaintHint, PaintViewer, SaveViewer],
ViewerPrivate USING [AcquireWriteLocks, ReleaseWriteLocks],
ViewerTools USING [GetContents, GetSelectionContents, SelPos, SelPosRec, SetSelection],
WorldVM USING [LocalWorld];
TEditDocuments2Impl: CEDAR MONITOR
IMPORTS AMMiniModel, BasicTime, Convert, FS, Menus, MessageWindow, NodeProps, Rope, TEditDocument, TEditDocumentPrivate, TextEdit, TextNode, TEditDisplay, TEditInput, TEditInputOps, TEditProfile, TEditSelection, TEditSelectionOps, VersionMap, VersionMapDefaults, ViewerForkers, ViewerGroupLocks, ViewerLocks, ViewerOps, ViewerPrivate, ViewerTools, WorldVM
EXPORTS TEditDocument, TEditDocumentPrivate, TEditOps
= BEGIN
ROPE: TYPE = Rope.ROPE;
Viewer: TYPE = ViewerClasses.Viewer;
File: TYPE = FS.OpenFile;
noFile: File = FS.nullOpenFile;
initialCaret: ViewerTools.SelPos ← NEW[ViewerTools.SelPosRec ← []];
ForceInitialCaret: PROC [viewer: Viewer] = {
ViewerTools.SetSelection[viewer, initialCaret];
};
lastRoot: TextNode.Ref;
lastViewer: Viewer;
GetViewerForRootI: INTERNAL PROC [root: TextNode.Ref] RETURNS [viewer: Viewer] = {
IF root = NIL THEN RETURN [NIL];
IF root = lastRoot THEN RETURN [lastViewer];
lastRoot ← root;
RETURN [lastViewer ← NARROW[NodeProps.GetProp[root,$Viewer]]];
};
RecordViewerForRootI: INTERNAL PROC [viewer: Viewer, root: TextNode.Ref] = {
NodeProps.PutProp[root,$Viewer,viewer];
IF root = lastRoot THEN lastViewer ← viewer; -- keep the cache up to date
};
GetViewerForRoot: PUBLIC ENTRY PROC [root: TextNode.Ref] RETURNS [viewer: Viewer] = {
ENABLE UNWIND => NULL;
RETURN [GetViewerForRootI[root]];
};
RecordViewerForRoot: PUBLIC ENTRY PROC [viewer: Viewer, root: TextNode.Ref] = {
ENABLE UNWIND => NULL;
RecordViewerForRootI[viewer, root];
};
ForgetViewer: PUBLIC ENTRY PROC [viewer: Viewer] = {
ENABLE UNWIND => NULL;
tdd: TEditDocument.TEditDocumentData = NARROW[viewer.data];
root: TextNode.Ref;
IF tdd=NIL THEN RETURN;
root ← tdd.text;
IF GetViewerForRootI[root] # viewer THEN RETURN;
IF viewer.link # NIL THEN {
change to a linked viewer
RecordViewerForRootI[viewer.link,root];
RETURN;
};
RecordViewerForRootI[NIL,root];
};
LoadHistory: TYPE = RECORD[name: ROPE, place: INT];
CopyLoadHistory: PUBLIC PROC [from, to: Viewer] = {
old: REF LoadHistory ← NARROW[ViewerOps.FetchProp[from, $LoadHistory]];
new: REF LoadHistory ← NARROW[ViewerOps.FetchProp[to, $LoadHistory]];
IF old=NIL THEN RETURN;
IF new=NIL THEN {
new ← NEW[LoadHistory];
ViewerOps.AddProp[to, $LoadHistory, new] };
new^ ← old^;
};
AllocLoadHistory: PROC [viewer: Viewer] RETURNS [prop: REF LoadHistory] = {
IF (prop ← NARROW[ViewerOps.FetchProp[viewer, $LoadHistory]]) # NIL THEN RETURN;
prop ← NEW[LoadHistory];
ViewerOps.AddProp[viewer, $LoadHistory, prop];
};
SetLoadHistoryInfo: PROC [viewer: Viewer, prop: REF LoadHistory] = {
tdd: TEditDocument.TEditDocumentData ← NARROW[viewer.data];
IF tdd = NIL THEN RETURN;
prop.name ← viewer.name;
prop.place ← TextNode.LocNumber[tdd.lineTable.lines[0].pos];
};
SetLoadHistory: PROC [parent, viewer: Viewer] = {
Make viewer's load history point to current contents of parent
prop: REF LoadHistory ← AllocLoadHistory[viewer];
SetLoadHistoryInfo[parent, prop];
};
SaveLoadHistory: PROC [viewer: Viewer] = {
prop: REF LoadHistory ← AllocLoadHistory[viewer];
SetLoadHistoryInfo[viewer, prop];
};
PositionHistory: TYPE = TEditDocumentPrivate.PositionHistory;
CopyPositionHistory: PUBLIC PROC [from, to: Viewer] = {
old: REF PositionHistory ← NARROW[ViewerOps.FetchProp[from, $PositionHistory]];
new: REF PositionHistory ← NARROW[ViewerOps.FetchProp[to, $PositionHistory]];
IF old=NIL THEN RETURN;
IF new=NIL THEN {
new ← NEW[PositionHistory];
ViewerOps.AddProp[to, $PositionHistory, new];
};
new^ ← old^;
};
ClearPositionHistory: PROC [viewer: Viewer] = {
prop: REF PositionHistory ← NARROW[ViewerOps.FetchProp[viewer, $PositionHistory]];
IF prop = NIL THEN RETURN;
prop.pos ← prop.prev ← TextNode.nullLocation;
};
RememberCurrentPosition: PUBLIC PROC [viewer: Viewer] = {
tdd: TEditDocument.TEditDocumentData ← NARROW[viewer.data];
prop: REF PositionHistory ← NARROW[ViewerOps.FetchProp[viewer, $PositionHistory]];
loc: TextNode.Location;
IF tdd=NIL THEN RETURN;
IF prop = NIL THEN {
prop ← NEW[PositionHistory];
ViewerOps.AddProp[viewer, $PositionHistory, prop] };
[] ← TEditDocument.SpinAndLock[tdd, "RememberCurrentPosition"];
loc ← tdd.lineTable.lines[0].pos;
TEditDocument.Unlock[tdd];
IF loc = prop.pos THEN RETURN;
prop.prev ← prop.pos; prop.pos ← loc;
};
PositionViewer: PUBLIC PROC [viewer: Viewer, loc: TextNode.Location, hint: ViewerOps.PaintHint ← client] RETURNS [ok: BOOLFALSE] = {
ok ← PositionViewerInternal[viewer, loc];
IF ok THEN ForkViewerPainter[viewer, hint];
};
PositionViewerInternal: PROC [viewer: Viewer, loc: TextNode.Location] RETURNS [ok: BOOLFALSE] = {
CheckPosition: PROC [viewer: Viewer, loc: TextNode.Location] RETURNS [good: BOOL, goodloc: TextNode.Location] = {
root, node: TextNode.Ref;
t1: TextNode.RefTextNode;
tdd: TEditDocument.TEditDocumentData;
IF viewer=NIL OR viewer.destroyed OR (node ← loc.node)=NIL THEN GOTO Failed;
IF (tdd ← NARROW[viewer.data]) = NIL THEN GOTO Failed;
IF (root ← tdd.text)=NIL THEN GOTO Failed;
IF TextNode.Root[node] # root THEN GOTO Failed; -- make sure still in the tree
IF (t1 ← TextNode.NarrowToTextNode[node])=NIL
OR loc.where NOT IN [0..TextEdit.Size[t1]] THEN
RETURN [TRUE, [node,0]];
RETURN [TRUE, loc];
EXITS Failed => RETURN [FALSE, TextNode.nullLocation];
};
WITH viewer.data SELECT FROM
tdd: TEditDocument.TEditDocumentData => {
node: TextNode.RefTextNode;
[ok, loc] ← CheckPosition[viewer, loc];
IF ok THEN {
RememberCurrentPosition[viewer];
IF (node ← TextNode.NarrowToTextNode[loc.node]) # NIL THEN {
backup to line start
where: INTMAX[0, MIN[loc.where, TextEdit.Size[node]-1]];
backStop: INTMAX[0, where-300];
UNTIL where<=backStop OR Rope.Fetch[node.rope, where-1]=15C DO
where ← where - 1;
ENDLOOP;
loc.where ← where;
};
TEditDisplay.EstablishLine[tdd, loc];
};
};
ENDCASE;
};
CloseAndForkPaint: PROC [viewer: Viewer] = TRUSTED {
ViewerOps.CloseViewer[viewer, FALSE];
ViewerForkers.ForkPaint[viewer, all];
};
ForkViewerPainter: PROC [viewer: Viewer, hint: ViewerOps.PaintHint] = TRUSTED {
ViewerForkers.ForkPaint[viewer, hint];
};
ViewerPainter: PROC [viewer: Viewer, hint: ViewerOps.PaintHint] = {
ENABLE ABORTED => CONTINUE;
All calls in this module to ViewerOps.PaintViewer go through here to aid debugging.
(note that calls to ComputeColumn also paint the screen)
ViewerOps.PaintViewer[viewer, hint];
};
KillSelections: PUBLIC PROC [parent: Viewer] = {
OPEN TEditSelection;
IF pSel # NIL AND pSel.viewer = parent THEN MakeSelection[selection: primary];
IF sSel # NIL AND sSel.viewer = parent THEN MakeSelection[selection: secondary];
IF fSel # NIL AND fSel.viewer = parent THEN MakeSelection[selection: feedback];
};
CancelLinks: PUBLIC PROC [viewer: Viewer] = {
ForgetViewer[viewer];
IF viewer.link.link=viewer
THEN {
viewer.link.link ← NIL;
ForkViewerPainter[viewer.link, caption];
}
ELSE FOR v: Viewer ← viewer.link.link, v.link UNTIL v.link=viewer DO
REPEAT FINISHED => v.link ← viewer.link;
ENDLOOP;
viewer.link ← NIL;
};
DefaultMenus: PUBLIC PROC [viewer: Viewer, paint: BOOLFALSE] = {
menu: Menus.Menu ← viewer.menu;
num: INTEGER ← 1;
DoLine: PROC [which: TEditProfile.DefaultMenuChoice] = {
entry: Menus.MenuEntry ← SELECT which FROM
places => TEditDocumentPrivate.findMenu,
levels => TEditDocumentPrivate.levelMenu,
ENDCASE => NIL;
IF entry = NIL THEN RETURN;
Menus.SetLine[menu, num, entry];
num ← num+1 };
DoLine[TEditProfile.menu1];
DoLine[TEditProfile.menu2];
DoLine[TEditProfile.menu3];
ViewerOps.EstablishViewerPosition[viewer, viewer.wx, viewer.wy, viewer.ww, viewer.wh];
IF paint THEN ForkViewerPainter[viewer, all];
};
MakeNewViewer: PROC [parent: Viewer, wDir: ROPENIL] RETURNS [viewer: Viewer] = {
Makes a new, empty, $Text viewer, placing it under the parent viewer (if any).
viewer ← MakeNewIcon[parent, wDir];
IF parent = NIL
THEN ViewerOps.OpenIcon[icon: viewer, paint: TRUE]
ELSE {
col: ViewerClasses.Column = parent.column;
inner: PROC = {
IF parent.destroyed THEN RETURN;
IF parent.iconic THEN RETURN;
IF NOT viewer.iconic THEN RETURN;
IF parent.column # col THEN RETURN;
copy height and position info from old to new
viewer.openHeight ← parent.openHeight;
ViewerOps.OpenIcon[icon: viewer, paint: FALSE];
ViewerOps.MoveBelowViewer[static: parent, altered: viewer, paint: FALSE];
ViewerOps.ComputeColumn[col];
};
IF TEditProfile.openFirstLevelOnly THEN
WITH viewer.data SELECT FROM
tdd: TEditDocument.TEditDocumentData => tdd.clipLevel ← 1;
ENDCASE;
ViewerLocks.CallUnderColumnLocks[inner, col, static];
};
};
MakeNewIcon: PROC [parent: Viewer, wDir: ROPENIL] RETURNS [viewer: Viewer] = {
... returns a new iconic $Text viewer with default menus, no painting done.
IF wDir=NIL THEN wDir ← WorkingDirectoryFromViewer[parent];
viewer ← ViewerOps.CreateViewer[
flavor: $Text,
info: [name: wDir, column: IF parent=NIL THEN left ELSE parent.column, iconic: TRUE],
paint: FALSE];
IF TEditProfile.openFirstLevelOnly THEN
WITH viewer.data SELECT FROM
tdd: TEditDocument.TEditDocumentData => tdd.clipLevel ← 1;
ENDCASE;
DefaultMenus[viewer];
};
ReplaceByNewViewer: PROC [parent: Viewer, wDir: ROPENIL] RETURNS [viewer: Viewer] = {
viewer ← MakeNewIcon[parent, wDir];
SwapViewers[parent, viewer];
};
SwapViewers: PROC [parent: Viewer, icon: Viewer] = {
Must lock both columns
inner: PROC = {
IF parent.destroyed THEN RETURN;
IF parent.iconic THEN RETURN;
IF NOT icon.iconic THEN RETURN;
IF parent.column # col THEN RETURN;
copy height and position info from old to new
icon.openHeight ← parent.openHeight;
ViewerOps.OpenIcon[icon: icon, paint: FALSE];
ViewerOps.MoveBelowViewer[static: parent, altered: icon, paint: FALSE];
CloseAndForkPaint[parent];
ViewerOps.ComputeColumn[col];
};
col: ViewerClasses.Column = parent.column;
KillSelections[parent]; -- need to deselect before making new viewer
ViewerLocks.CallUnderColumnLocks[inner, col, static];
};
FindOldViewer: PROC [name: ROPE, viewer: Viewer] RETURNS [old: Viewer ← NIL] = {
Match: ViewerOps.EnumProc -- PROC[v: Viewer] RETURNS[BOOL ← TRUE] -- = {
IF v#viewer AND Rope.Equal[name, v.name, FALSE] THEN { old ← v; RETURN [FALSE] };
};
ViewerOps.EnumerateViewers[Match];
};
Report: PROC [m1, m2, m3, m4: ROPENIL, flash: BOOLFALSE] = {
ENABLE ABORTED => CONTINUE;
MessageWindow.Append[m1, TRUE];
IF m2#NIL THEN MessageWindow.Append[m2];
IF m3#NIL THEN MessageWindow.Append[m3];
IF m4#NIL THEN MessageWindow.Append[m4];
IF flash THEN MessageWindow.Blink[];
};
Flash: PROC [m1, m2: ROPENIL] = {
Report[m1: m1, m2: m2, flash: TRUE];
};
PleaseSelectFileName: PROC = {
Flash["Please select file name."];
};
IllegalFileName: ViewerForkers.CallBack = {
Flash["Illegal file name."];
};
ReloadedMessage: ViewerForkers.CallBack = {
WITH data SELECT FROM
name: ROPE => Flash[name," restored with previous unsaved edits."];
ENDCASE;
};
RemoveVersion: PROC [x: ROPE] RETURNS [ROPE] = {
RETURN [x.Flatten[len: x.SkipTo[skip: "!"]]];
};
GetFileName: PROC [file: FS.OpenFile, removeVersion: BOOLFALSE] RETURNS [name: ROPENIL] = {
name ← FS.GetName[file ! FS.Error => CONTINUE].fullFName;
IF name#NIL AND removeVersion THEN name ← RemoveVersion[name];
};
LoadOp: TYPE ~ {load, open, replace};
LookupType: TYPE ~ {source, impl};
DoGet: PROC [parent: Viewer, file: FS.OpenFile, specificVersion: BOOL, op: LoadOp ← $load, place: INT ← 0, search: ROPENIL, forceOpen: BOOLTRUE] RETURNS [viewer: Viewer] = {
viewerIn: Viewer ← parent;
needPaint: BOOLFALSE;
loaded: BOOLFALSE;
IF parent = NIL THEN op ← open;
SELECT op FROM
load => viewerIn ← parent;
open => viewerIn ← MakeNewIcon[parent];
replace => viewerIn ← ReplaceByNewViewer[parent];
ENDCASE => ERROR;
[viewer, loaded] ← DoLoad[viewer: viewerIn, file: file, op: op, specificVersion: specificVersion, place: place];
IF viewer # NIL THEN {
We were able to do the load
IF viewer # viewerIn THEN {
Humph! A previous viewer existed!
Report[viewer.name, " was already loaded."];
SELECT op FROM
open, replace => TRUSTED {
We created a viewer to hold the result, but we will never use it now.
ViewerForkers.ForkCall[viewerIn, FlameOut, viewerIn];
viewerIn ← NIL};
ENDCASE;
};
IF Rope.Size[search] # 0 THEN WITH viewer.data SELECT FROM
ntdd: TEditDocument.TEditDocumentData => {
We need to perform a search for the given definition.
loc: TextNode.Location ← TextNode.LocWithin[ntdd.text, 0];
sel: TEditDocument.Selection ← NIL;
IF Rope.Match["|*", search]
THEN {
This is a position spec to parse
pos: INT ← Convert.IntFromRope[Rope.Flatten[search, 1]
! Convert.Error => GO TO faulty];
nloc: TextNode.Location ← TextNode.LocWithin[n: ntdd.text, count: pos, skipCommentNodes: TRUE];
[] ← PositionViewerInternal[viewer, nloc];
TEditSelectionOps.ShowGivenPosition[viewer, pos];
EXITS faulty => {};
}
ELSE {
This is a definition to search for
TEditDisplay.EstablishLine[ntdd, loc];
TEditSelection.FindRope[
viewer: viewer, rope: search, case: TRUE, word: TRUE, def: TRUE, id: feedback];
sel ← TEditSelection.fSel;
IF sel # NIL AND sel.viewer = viewer THEN
capture the location found and establish it as the place to display
loc ← sel.start.pos;
TEditDisplay.EstablishLine[ntdd, loc];
};
RememberCurrentPosition[viewer];
needPaint ← TRUE;
};
ENDCASE;
SELECT TRUE FROM
viewer.iconic AND forceOpen => {
RRA: For some stupid reason we have to open the icon first before moving things. I wonder why?
ViewerOps.OpenIcon[viewer];
IF parent # NIL AND parent # viewer AND NOT parent.destroyed THEN
IF op = replace
THEN CloseAndForkPaint[parent]
ELSE ViewerOps.MoveBelowViewer[viewer, parent, TRUE];
};
loaded =>
ForkViewerPainter[viewer, all];
needPaint =>
ForkViewerPainter[viewer, client];
ENDCASE;
};
};
DoLoad: PROC [viewer: Viewer, file: FS.OpenFile, op: LoadOp, specificVersion: BOOLFALSE, place: INT ← 0] RETURNS [out: Viewer, loaded: BOOLFALSE] = {
Load a file into a viewer
viewer is the viewer to be loaded with the contents of the file
name is the specified name for the file
file is the open file
clearMessage: BOOLTRUE;
fileName: ROPE ~ FS.GetName[file].fullFName;
name: ROPE ← fileName;
out ← viewer;
IF NOT specificVersion THEN name ← name.Flatten[len: name.SkipTo[skip: "!"]];
IF Rope.Equal[viewer.file, fileName, FALSE] THEN {
Report[fileName, " is already loaded."]; RETURN;
};
Report["Loading ", name];
KillSelections[viewer];
WITH viewer.data SELECT FROM
tdd: TEditDocument.TEditDocumentData => {
oldViewer: Viewer ← NIL;
linkViewer: Viewer ← NIL;
vFile: ROPENIL;
DO
oldViewer ← FindOldViewer[name, viewer];
IF oldViewer # NIL THEN linkViewer ← oldViewer.link;
ViewerPrivate.AcquireWriteLocks[viewer, oldViewer, linkViewer];
IF oldViewer = FindOldViewer[name, viewer] AND (oldViewer = NIL OR linkViewer = oldViewer.link) THEN EXIT;
Note that if anything changed while we were waiting for the lock we should release our locks, then go around and try again
ViewerPrivate.ReleaseWriteLocks[viewer, oldViewer, linkViewer];
ENDLOOP;
{
ENABLE UNWIND => ViewerPrivate.ReleaseWriteLocks[viewer, oldViewer, linkViewer];
[] ← TEditDocument.SpinAndLock[tdd, "DoLoad"];
Can this deadlock? We hope not!
IF (vFile ← viewer.file) #NIL THEN SaveLoadHistory[viewer];
IF oldViewer # NIL THEN {
IF NOT oldViewer.destroyed
THEN WITH oldViewer.data SELECT FROM
tddOld: TEditDocument.TEditDocumentData => GO TO oldCase;
An old viewer was found, so don't do the load!
ENDCASE;
EXITS oldCase => {};
};
SELECT TRUE FROM
oldViewer = NIL => {
There is no other viewer with the same name
root: TextNode.Ref ~ TEditDocumentPrivate.FindUnsavedDocument[fileName];
IF viewer.link=NIL
THEN {
ForgetViewer[viewer]; -- remove this from the root => viewer mapping
IF viewer.newVersion AND (NOT viewer.saveInProgress) AND vFile#NIL
THEN TEditDocumentPrivate.RecordUnsavedDocument[vFile, tdd.text]
ELSE TEditInput.FreeTree[tdd.text];
}
ELSE
CancelLinks[viewer];
viewer.name ← name;
viewer.file ← fileName;
viewer.newVersion ← viewer.newFile ← FALSE;
IF root # NIL THEN {
ViewerForkers.ForkCall[NIL, ReloadedMessage, name];
clearMessage ← FALSE;
};
tdd.text ← NIL; -- so InitViewerDoc won't worry about freeing it
TEditDocumentPrivate.InitViewerDocInternal[viewer, file, root];
viewer.newFile ← FALSE;
viewer.newVersion ← root#NIL;
loaded ← TRUE;
ClearPositionHistory[viewer];
[] ← PositionViewerInternal[viewer, TextNode.LocWithin[tdd.text, place]];
RememberCurrentPosition[viewer];
};
ENDCASE => {
Another viewer has this name, so return it (caller will handle this case)
out ← oldViewer;
};
TEditDocument.Unlock[tdd];
ViewerPrivate.ReleaseWriteLocks[viewer, oldViewer, linkViewer];
};
IF clearMessage THEN MessageWindow.Clear[];
};
ENDCASE;
};
FlameOut: ViewerForkers.CallBack = {
ENABLE ABORTED => CONTINUE;
WITH data SELECT FROM
v: Viewer => ViewerOps.DestroyViewer[v];
ENDCASE;
};
TryToOpen: PROC [name: ROPE, wDir: ROPENIL] RETURNS [file: FS.OpenFile ← FS.nullOpenFile] = {
file ← FS.Open[name: name, wDir: wDir
! FS.Error => IF error.code=$unknownFile THEN CONTINUE];
};
TryExtensions: PROC [name: ROPE, wDir: ROPE, extensions: LIST OF ROPE] RETURNS [file: FS.OpenFile ← FS.nullOpenFile] = {
base: ROPE ~ name.Concat["."];
FOR list: LIST OF ROPE ← extensions, list.rest UNTIL list=NIL DO
file ← TryToOpen[name: base.Concat[list.first], wDir: wDir];
IF file#FS.nullOpenFile THEN EXIT;
ENDLOOP;
};
IsAnExtension: PROC [ext: ROPE, extensions: LIST OF ROPE] RETURNS [BOOL] = {
FOR list: LIST OF ROPE ← extensions, list.rest UNTIL list=NIL DO
IF Rope.Equal[ext, list.first, FALSE] THEN RETURN [TRUE];
ENDLOOP;
RETURN [FALSE];
};
FileNameProc: TYPE = TEditOps.FileNameProc;
fileNameProc: FileNameProc ← NIL;
RegisterFileNameProc: PUBLIC PROC [proc: FileNameProc] = {
fileNameProc ← proc;
};
ReplaceFileNameProc: PUBLIC PROC [new: FileNameProc] RETURNS [old: FileNameProc] = {
old ← fileNameProc;
fileNameProc ← new;
};
TryVersionMap: PROC [shortName: ROPE] RETURNS [file: FS.OpenFile ← FS.nullOpenFile] = {
Adapted from VersionMapCommandsImpl.FindSource.
Stops at the first name it finds, if any.
mapList: VersionMap.MapList ~ VersionMapDefaults.GetMapList[$Source];
ranges: VersionMap.RangeList ~ VersionMap.ShortNameToRanges[mapList, shortName];
bestName: ROPENIL;
bestDate: BasicTime.GMT ← BasicTime.nullGMT;
FOR list: VersionMap.RangeList ← ranges, list.rest UNTIL list=NIL DO
range: VersionMap.Range ← list.first;
WHILE range.len # 0 DO
fullName: ROPE;
created: BasicTime.GMT;
[name: fullName, created: created, next: range] ← VersionMap.RangeToEntry[range];
IF bestDate = BasicTime.nullGMT OR BasicTime.Period[from: bestDate, to: created] > 0 THEN {bestDate ← created; bestName ← fullName};
ENDLOOP;
ENDLOOP;
IF bestName # NIL THEN
RETURN [FS.Open[name: bestName, wantedCreatedTime: bestDate]];
};
TryVersionMapExtensions: PROC [name: ROPE, extensions: LIST OF ROPE] RETURNS [file: FS.OpenFile ← FS.nullOpenFile] = {
base: ROPE ~ name.Concat["."];
FOR list: LIST OF ROPE ← extensions, list.rest UNTIL list=NIL DO
file ← TryVersionMap[base.Concat[list.first]];
IF file#FS.nullOpenFile THEN EXIT;
ENDLOOP;
};
FileNotFound: ERROR[fileName: ROPE] = CODE;
LookupSource: PROC [sel: ROPE, wDir: ROPE, fileNameProcViewer: Viewer ← NIL] RETURNS [file: FS.OpenFile ← FS.nullOpenFile, specificVersion: BOOLFALSE, search: ROPENIL] = {
dot: INT ← 0;
hasExtension, standardExtension, simpleName: BOOLFALSE;
base, ext: ROPENIL;
proc: FileNameProc ~ fileNameProc;
Report["Directory lookup for ", sel];
The first pass is for the position specification (if any)
FOR i: INT DECREASING IN[0..sel.Size[]) DO
SELECT sel.Fetch[i] FROM
'| => {search ← Rope.Flatten[sel, i]; sel ← Rope.Flatten[sel, 0, i]; EXIT};
IN ['0..'9] => {};
ENDCASE => EXIT;
ENDLOOP;
FOR i: INT DECREASING IN[0..sel.Size[]) DO
SELECT sel.Fetch[i] FROM
'! => specificVersion ← TRUE;
'. => IF hasExtension THEN NULL ELSE { dot ← i; hasExtension ← TRUE };
'], '>, '/ => EXIT;
ENDCASE;
REPEAT FINISHED => simpleName ← TRUE;
ENDLOOP;
file ← TryToOpen[name: sel, wDir: wDir];
IF file#FS.nullOpenFile THEN RETURN;
IF hasExtension OR specificVersion THEN NULL
ELSE {
file ← TryExtensions[name: sel, wDir: wDir, extensions: TEditProfile.sourceExtensions];
IF file#FS.nullOpenFile THEN RETURN;
};
IF proc#NIL THEN {
pname, psearch: ROPE;
[pname, psearch] ← proc[sel, fileNameProcViewer];
IF pname.Size[]#0 THEN {
file ← TryToOpen[name: pname, wDir: wDir];
IF file#FS.nullOpenFile THEN { search ← psearch; RETURN };
};
};
IF specificVersion THEN RETURN;
IF hasExtension THEN {
base ← sel.Substr[len: dot];
ext ← sel.Substr[start: dot+1];
IF IsAnExtension[ext, TEditProfile.sourceExtensions] THEN standardExtension ← TRUE;
IF NOT standardExtension THEN {
file ← TryExtensions[name: base, wDir: wDir, extensions: TEditProfile.implExtensions];
IF file#FS.nullOpenFile THEN { search ← ext; RETURN };
};
};
IF simpleName AND TEditProfile.tryVersionMap THEN {
Report["Get: trying version map for ", sel];
file ← TryVersionMap[sel]; -- try sel verbatim first
IF file#FS.nullOpenFile THEN RETURN;
IF NOT hasExtension THEN {
file ← TryVersionMapExtensions[sel, TEditProfile.sourceExtensions];
IF file#FS.nullOpenFile THEN RETURN;
}
ELSE IF NOT standardExtension THEN {
file ← TryVersionMapExtensions[base, TEditProfile.implExtensions];
IF file#FS.nullOpenFile THEN { search ← ext; RETURN };
};
};
};
LookupImpl: PROC [sel: ROPE, wDir: ROPE] RETURNS [file: FS.OpenFile ← FS.nullOpenFile, search: ROPENIL] = {
cp: FS.ComponentPositions;
simpleName: BOOL ~ sel.SkipTo[skip: "[]<>/"]=sel.Size[];
xname, base, ext, impl: ROPENIL;
[fullFName: xname, cp: cp] ← FS.ExpandName[name: sel, wDir: wDir];
base ← xname.Substr[start: cp.base.start, len: cp.base.length]; -- base part is defs name
ext ← xname.Substr[start: cp.ext.start, len: cp.ext.length]; -- extension part is search item
IF ext.Size[]#0 THEN {
Report["Model lookup for implementor of ", base, ".", ext];
TRUSTED{ impl ← AMMiniModel.ImplementorName[
defsName: base, itemName: ext, world: WorldVM.LocalWorld[]] };
};
IF impl=NIL THEN impl ← base.Concat["Impl"];
Report["Directory lookup for ", impl];
file ← TryExtensions[name: impl, wDir: xname.Substr[len: cp.base.start],
extensions: TEditProfile.implExtensions];
IF file#FS.nullOpenFile THEN { search ← ext; RETURN };
IF simpleName AND TEditProfile.tryVersionMap THEN {
Report["GetImpl: trying version map for ", impl];
file ← TryVersionMapExtensions[impl, TEditProfile.implExtensions];
IF file#FS.nullOpenFile THEN { search ← ext; RETURN };
};
ERROR FileNotFound[impl];
};
WorkingDirectoryFromViewer: PUBLIC PROC [parent: Viewer] RETURNS [ROPE] = {
name, fname: ROPENIL; cp: FS.ComponentPositions;
IF parent#NIL THEN name ← parent.name;
{
ENABLE FS.Error => CONTINUE;
IF name=NIL OR parent.file=NIL
THEN [fullFName: fname, cp: cp] ← FS.ExpandName[name: "*", wDir: name ! FS.Error => CONTINUE]
ELSE [fullFName: fname, cp: cp] ← FS.ExpandName[name: name ! FS.Error => CONTINUE];
RETURN [fname.Substr[len: cp.base.start]];
};
RETURN [FS.GetDefaultWDir[]];
};
IsAWorkingDirectory: PROC [name: ROPE] RETURNS [BOOL] = {
RETURN [Rope.Match["*/", name] OR Rope.Match["*>", name]];
};
CanonicalWorkingDirectory: PROC [txt: ROPE, wDir: ROPENIL] RETURNS [ROPE] = {
fname: ROPENIL; cp: FS.ComponentPositions;
[fullFName: fname, cp: cp] ← FS.ExpandName[name: txt.Concat["*"], wDir: wDir
! FS.Error => CONTINUE];
IF fname=NIL THEN RETURN [NIL] ELSE RETURN [fname.Substr[len: cp.base.start]];
};
DoGetFile: PROC [txt: ROPE, parent: Viewer, op: LoadOp ← $load, lookup: LookupType ← $source, fileNameProcViewer: Viewer ← NIL] RETURNS [viewer: Viewer ← NIL] = {
parentDir: ROPE ~ WorkingDirectoryFromViewer[parent];
file: FS.OpenFile ← FS.nullOpenFile;
specificVersion: BOOLFALSE;
search: ROPENIL;
IF txt=NIL THEN txt ← ViewerTools.GetSelectionContents[];
IF fileNameProcViewer=NIL THEN fileNameProcViewer ← parent;
IF txt.Size[]=0 THEN { PleaseSelectFileName[]; GOTO Fail };
IF IsAWorkingDirectory[txt] THEN {
wDir: ROPE ~ CanonicalWorkingDirectory[txt, parentDir];
IF wDir=NIL THEN { Flash[txt, " is an illegal working directory."]; GOTO Fail };
SELECT op FROM
load => DoEmptyViewer[parent: viewer ← parent, wDir: wDir];
open => viewer ← MakeNewViewer[parent: parent, wDir: wDir];
replace => viewer ← ReplaceByNewViewer[parent: parent, wDir: wDir];
ENDCASE => ERROR;
RETURN [viewer];
};
{
ENABLE {
FS.Error => { Flash[error.explanation]; GOTO Fail };
FileNotFound => { Flash[fileName, " not found."]; GOTO Fail };
};
SELECT lookup FROM
$source => [file: file, specificVersion: specificVersion, search: search] ←
LookupSource[sel: txt, wDir: parentDir, fileNameProcViewer: fileNameProcViewer];
$impl => [file: file, search: search] ← LookupImpl[sel: txt, wDir: parentDir];
ENDCASE => ERROR;
IF file=FS.nullOpenFile THEN { Flash[txt, " not found."]; GOTO Fail };
};
viewer ← DoGet[parent, file, specificVersion, op, 0, search];
EXITS Fail => RETURN [NIL];
};
The following are the top level procedures that implement the Tioga file operations: they are available as menu items, system buttons, or keystrokes (LF).
Clear:
EmptyViewer[viewer]
NewViewer[viewer]
CloseAndNewViewer[viewer]
Reset:
TEditDocuments3Impl.ResetOp[viewer]
Get:
[] ← DoLoadFile[viewer, selection, FALSE]
[] ← DoOpenFile[selection, viewer]
[] ← DoCloseAndOpenFile[viewer, selection]
GetImpl:
[] ← DoLoadImplFile[viewer, selection, FALSE]
[] ← DoOpenImplFile[selection, viewer]
[] ← DoCloseAndOpenImplFile[viewer, selection]
PrevFile:
LoadPreviousFile[viewer]
OpenPreviousFile[viewer]
CloseAndOpenPreviousFile[viewer]
Store:
DoStoreFile[viewer, selection]
Save:
TEditDocuments3Impl.SaveOp[viewer]
New button:
NewViewer[NIL]
Open button:
[] ← DoOpenFile[NIL, selection]
LF keyboard command:
AnonymousLoadFile[viewer]
AnonymousLoadImplFile[viewer]
DoEmptyViewer: PROC [parent: Viewer, wDir: ROPENIL] = {
inner: PROC [tdd: TEditDocument.TEditDocumentData] = {
link1: Viewer ← parent.link;
link2: Viewer ← NIL;
IF wDir=NIL THEN wDir ← WorkingDirectoryFromViewer[parent];
TEditSelection.LockSel[primary, "EmptyViewer"];
{
ENABLE UNWIND => TEditSelection.UnlockSel[primary];
prop: REF LoadHistory;
[] ← TEditDocument.SpinAndLock[tdd, "EmptyViewer"];
KillSelections[parent];
SaveLoadHistory[parent];
prop ← NARROW[ViewerOps.FetchProp[parent, $LoadHistory]]; -- hang onto it
SELECT TRUE FROM
link1 # NIL => CancelLinks[parent];
parent.newVersion AND ~parent.saveInProgress AND parent.file # NIL =>
TEditDocumentPrivate.RecordUnsavedDocument[parent.file, tdd.text];
ENDCASE => TEditInput.FreeTree[tdd.text];
parent.name ← wDir;
parent.file ← NIL;
parent.newVersion ← parent.newFile ← FALSE;
tdd.text ← NIL; -- so InitViewerDoc won't free it
TEditDocumentPrivate.InitViewerDoc[parent,NIL];
TEditDocument.Unlock[tdd];
ClearPositionHistory[parent];
ViewerOps.AddProp[parent, $LoadHistory, prop]; -- restore file history
ViewerPainter[parent, all];
ForceInitialCaret[parent];
};
TEditSelection.UnlockSel[primary];
};
IF parent # NIL THEN LockTheWorks[inner, parent, "EmptyViewer"];
};
EmptyViewer: PUBLIC PROC [parent: Viewer] = {
DoEmptyViewer[parent];
};
DoNewViewer: PUBLIC PROC [parent: Viewer ← NIL] RETURNS [new: Viewer] = {
TEditSelection.LockSel[primary, "DoNewViewer"];
{
ENABLE UNWIND => TEditSelection.UnlockSel[primary];
new ← MakeNewViewer[parent];
ForceInitialCaret[new];
};
TEditSelection.UnlockSel[primary];
};
NewViewer: PUBLIC PROC [parent: Viewer] = {
[] ← DoNewViewer[parent];
};
DoCloseAndNewViewer: PUBLIC PROC [parent: Viewer] RETURNS [new: Viewer] = {
TEditSelection.LockSel[primary, "DoCloseAndNewViewer"];
{ ENABLE UNWIND => TEditSelection.UnlockSel[primary];
new ← ReplaceByNewViewer[parent];
ForceInitialCaret[new];
};
TEditSelection.UnlockSel[primary];
};
CloseAndNewViewer: PUBLIC PROC [parent: Viewer] = {
[] ← DoCloseAndNewViewer[parent];
};
DoLoadFile: PUBLIC PROC [parent: Viewer, fileName: ROPENIL, close: BOOLFALSE,
fileNameProcViewer: Viewer ← NIL] RETURNS [viewer: Viewer] = {
viewer ← DoGetFile[txt: fileName, parent: parent,
op: IF close THEN $replace ELSE $load, lookup: $source,
fileNameProcViewer: fileNameProcViewer];
};
LoadFile: PUBLIC PROC [parent: Viewer] = {
[] ← DoLoadFile[parent: parent, fileName: NIL, close: FALSE];
};
DoOpenFile: PUBLIC PROC
[fileName: ROPENIL, parent: Viewer, fileNameProcViewer: Viewer ← NIL]
RETURNS [viewer: Viewer] = {
viewer ← DoGetFile[
txt: fileName, parent: parent, op: $open,
lookup: $source, fileNameProcViewer: fileNameProcViewer];
};
OpenFile: PUBLIC PROC [parent: Viewer] = {
[] ← DoOpenFile[fileName: NIL, parent: parent];
};
DoCloseAndOpenFile: PUBLIC PROC [parent: Viewer, fileName: ROPENIL] RETURNS [viewer: Viewer] = {
viewer ← DoGetFile[txt: fileName, parent: parent, op: $replace, lookup: $source];
};
CloseAndOpenFile: PUBLIC PROC [parent: Viewer, fileNameProcViewer: Viewer ← NIL] = {
[] ← DoGetFile[txt: NIL, parent: parent, op: $replace, lookup: $source,
fileNameProcViewer: fileNameProcViewer];
};
DoLoadImplFile: PUBLIC PROC [parent: Viewer, fileName: ROPENIL, close: BOOLFALSE] RETURNS [viewer: Viewer] = {
viewer ← DoGetFile[txt: fileName, parent: parent,
op: IF close THEN $replace ELSE $load, lookup: $impl];
};
LoadImplFile: PUBLIC PROC [parent: Viewer] = {
[] ← DoLoadImplFile[parent: parent, fileName: NIL];
};
DoOpenImplFile: PUBLIC PROC [fileName: ROPENIL, parent: Viewer ← NIL] RETURNS [viewer: Viewer] = {
viewer ← DoGetFile[txt: fileName, parent: parent, op: $open, lookup: $impl];
};
OpenImplFile: PUBLIC PROC [parent: Viewer] = {
[] ← DoOpenImplFile[fileName: NIL, parent: parent];
};
DoCloseAndOpenImplFile: PUBLIC PROC [parent: Viewer, fileName: ROPENIL] RETURNS [viewer: Viewer] = {
viewer ← DoGetFile[txt: fileName, parent: parent, op: $replace, lookup: $impl];
};
CloseAndOpenImplFile: PUBLIC PROC [parent: Viewer] = {
[] ← DoCloseAndOpenImplFile[parent, NIL];
};
PreLoadPrevious: PUBLIC Menus.MenuProc = { -- called when unguarding PrevFile
viewer: Viewer = NARROW[parent];
tdd: TEditDocument.TEditDocumentData;
prop: REF LoadHistory;
propName: ROPE;
tdd ← NARROW[viewer.data];
IF tdd = NIL THEN RETURN;
[] ← TEditDocument.SpinAndLock[tdd, "PreLoadPrevious"];
delay until after other op completes
prop ← NARROW[ViewerOps.FetchProp[viewer, $LoadHistory]];
TEditDocument.Unlock[tdd];
IF prop=NIL OR Rope.Equal[prop.name, viewer.file, FALSE] THEN {
Flash["No record of previous file loaded in this viewer"];
RETURN;
};
propName ← prop.name;
Report[propName," ~ Click LEFT to load, MIDDLE for new, RIGHT for close & new"];
};
DoLoadPreviousFile: PROC [parent: Viewer, op: LoadOp] = {
WITH parent.data SELECT FROM
tdd: TEditDocument.TEditDocumentData => {
prop: REF LoadHistory;
propName: ROPE;
propPlace: INT;
file: FS.OpenFile;
[] ← TEditDocument.SpinAndLock[tdd, "DoLoadPreviousFile"];
delay until after other op completes
prop ← NARROW[ViewerOps.FetchProp[parent, $LoadHistory]];
TEditDocument.Unlock[tdd];
IF prop = NIL OR Rope.Equal[prop.name, parent.file, FALSE] THEN {
Flash["No record of previous file loaded in this viewer"];
RETURN };
propName ← prop.name; propPlace ← prop.place;
file ← FS.Open[propName ! FS.Error => { Flash[error.explanation]; GOTO Fail }];
[] ← DoGet[parent: parent, file: file, specificVersion: (propName.Find["!"]>=0), op: op, place: propPlace, forceOpen: FALSE];
};
ENDCASE;
EXITS Fail => NULL;
};
LoadPreviousFile: PUBLIC PROC [parent: Viewer] = {
DoLoadPreviousFile[parent: parent, op: $load];
};
OpenPreviousFile: PUBLIC PROC [parent: Viewer] = {
DoLoadPreviousFile[parent: parent, op: $open];
};
CloseAndOpenPreviousFile: PUBLIC PROC [parent: Viewer] = {
DoLoadPreviousFile[parent: parent, op: $replace];
};
GetCreateName: PROC [parent: Viewer, name: ROPE] RETURNS [ROPENIL] = {
wDir: ROPE ~ WorkingDirectoryFromViewer[parent];
fullFName: ROPE; cp: FS.ComponentPositions;
[fullFName: fullFName, cp: cp] ← FS.ExpandName[name, wDir ! FS.Error => GOTO Fail];
RETURN [Rope.Flatten[base: fullFName, len: cp.ext.start+cp.ext.length]]; -- strip version
EXITS Fail => ViewerForkers.ForkCall[NIL, IllegalFileName];
};
IsNewFile: PROC [name: ROPE] RETURNS [new: BOOLFALSE] = {
[] ← FS.FileInfo[name ! FS.Error => { new ← TRUE; CONTINUE }];
};
PreStore: PUBLIC Menus.MenuProc = { -- called when unguarding Store
sel: ROPE ← ViewerTools.GetSelectionContents[];
fileName: ROPENIL; new: BOOLFALSE;
IF sel.Size[]=0 THEN {PleaseSelectFileName[]; RETURN};
fileName ← GetCreateName[NARROW[parent], sel];
IF fileName=NIL THEN RETURN;
new ← IsNewFile[fileName];
Report["Confirm Store to file: ", fileName, IF new THEN " [New File]" ELSE " [Old File]"];
};
DoStoreFile: PUBLIC PROC [parent: Viewer, fileName: ROPENIL] = {
inner: PROC [tdd: TEditDocument.TEditDocumentData] = {
linked: BOOL ← parent.link#NIL;
oldName: ROPE ~ parent.name;
oldFile: ROPE ~ parent.file;
IF fileName=NIL THEN fileName ← ViewerTools.GetSelectionContents[];
IF fileName.Size[]=0 THEN {PleaseSelectFileName[]; RETURN};
fileName ← GetCreateName[parent, fileName];
IF fileName=NIL THEN RETURN;
IF parent.file # NIL THEN SaveLoadHistory[parent];
IF linked THEN CancelLinks[parent]; -- remove viewer from link chain
parent.name ← parent.file ← fileName;
NodeProps.PutProp[tdd.text, $FileCreateDate, NIL]; -- remove so Save won't test
ViewerOps.SaveViewer[parent
! UNWIND => { parent.name ← oldName; parent.file ← oldFile }];
IF linked THEN {
copy tdd.text data structure
loc: INT;
KillSelections[parent];
loc ← TextNode.LocNumber[tdd.lineTable.lines[0].pos];
tdd.text ← NIL; -- so InitViewerDoc won't free it
TEditDocumentPrivate.InitViewerDoc[parent, NIL];
RememberCurrentPosition[parent];
ForkViewerPainter[parent, caption];
};
};
LockTheWorks[inner, parent, "StoreFile"];
};
LockTheWorks: PROC [inner: PROC [tdd: TEditDocument.TEditDocumentData], viewer: Viewer, who: ROPE] = {
innerWorks: PROC = {
WITH viewer.data SELECT FROM
tdd: TEditDocument.TEditDocumentData => {
[] ← TEditDocument.SpinAndLock[tdd, who];
inner[tdd ! UNWIND => TEditDocument.Unlock[tdd]];
TEditDocument.Unlock[tdd];
}
ENDCASE
};
ViewerGroupLocks.CallRootAndLinksUnderWriteLock[innerWorks, viewer];
};
StoreFile: PUBLIC PROC [parent: Viewer] = {
DoStoreFile[parent: parent, fileName: NIL];
};
GetViewerContents: PROC [viewer: Viewer] RETURNS [ROPE] = {
TEditInputOps.WaitForInsertToFinish[];
RETURN [ViewerTools.GetContents[viewer]];
};
DoAnonymousLoadFile: PROC [parent: Viewer, lookup: LookupType, fileNameProcViewer: Viewer ← NIL] = {
IF parent.file#NIL
THEN Flash["Viewer already contains a file."]
ELSE {
txt: ROPE ~ GetViewerContents[parent];
size: INT ~ txt.Size[];
IF size=0
THEN Flash["Enter a file name."]
ELSE {
v: Viewer ← DoGetFile[txt: txt, parent: parent, op: $load, lookup: lookup, fileNameProcViewer: fileNameProcViewer];
IF v#NIL AND v#parent THEN ViewerForkers.ForkCall[parent, FlameOut, parent];
};
};
};
AnonymousLoadFile: PUBLIC PROC [parent: Viewer, fileNameProcViewer: Viewer ← NIL] = {
DoAnonymousLoadFile[parent: parent, lookup: $source, fileNameProcViewer: fileNameProcViewer];
};
AnonymousLoadImplFile: PUBLIC PROC [parent: Viewer] = {
DoAnonymousLoadFile[parent: parent, lookup: $impl];
};
NodeProps.Register[$Viewer,
NodeProps.NullRead, NodeProps.NullWrite, NodeProps.NullCopy];
NodeProps.Register[$LockedViewer,
NodeProps.NullRead, NodeProps.NullWrite, NodeProps.NullCopy];
NodeProps.Register[$FileCreateDate,
NodeProps.NullRead, NodeProps.NullWrite, NodeProps.NullCopy];
NodeProps.Register[$FileExtension,
NodeProps.NullRead, NodeProps.NullWrite, NodeProps.NullCopy];
END.