<> <> <> <> <> <> <<>> 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 { <> 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] = { <> 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: BOOL _ FALSE] = { ok _ PositionViewerInternal[viewer, loc]; IF ok THEN ForkViewerPainter[viewer, hint]; }; PositionViewerInternal: PROC [viewer: Viewer, loc: TextNode.Location] RETURNS [ok: BOOL _ FALSE] = { 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 { <> where: INT _ MAX[0, MIN[loc.where, TextEdit.Size[node]-1]]; backStop: INT _ MAX[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; <> <<(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: BOOL _ FALSE] = { 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: ROPE _ NIL] RETURNS [viewer: Viewer] = { <> 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; <> 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: ROPE _ NIL] 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: ROPE _ NIL] RETURNS [viewer: Viewer] = { viewer _ MakeNewIcon[parent, wDir]; SwapViewers[parent, viewer]; }; SwapViewers: PROC [parent: Viewer, icon: Viewer] = { <> inner: PROC = { IF parent.destroyed THEN RETURN; IF parent.iconic THEN RETURN; IF NOT icon.iconic THEN RETURN; IF parent.column # col THEN RETURN; <> 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: ROPE _ NIL, flash: BOOL _ FALSE] = { 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: ROPE _ NIL] = { 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: BOOL _ FALSE] RETURNS [name: ROPE _ NIL] = { 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: ROPE _ NIL, forceOpen: BOOL _ TRUE] RETURNS [viewer: Viewer] = { viewerIn: Viewer _ parent; needPaint: BOOL _ FALSE; loaded: BOOL _ FALSE; 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 { <> IF viewer # viewerIn THEN { <> Report[viewer.name, " was already loaded."]; SELECT op FROM open, replace => TRUSTED { <> ViewerForkers.ForkCall[viewerIn, FlameOut, viewerIn]; viewerIn _ NIL}; ENDCASE; }; IF Rope.Size[search] # 0 THEN WITH viewer.data SELECT FROM ntdd: TEditDocument.TEditDocumentData => { <> loc: TextNode.Location _ TextNode.LocWithin[ntdd.text, 0]; sel: TEditDocument.Selection _ NIL; IF Rope.Match["|*", search] THEN { <> 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 { <> 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 <> loc _ sel.start.pos; TEditDisplay.EstablishLine[ntdd, loc]; }; RememberCurrentPosition[viewer]; needPaint _ TRUE; }; ENDCASE; SELECT TRUE FROM viewer.iconic AND forceOpen => { <> 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: BOOL _ FALSE, place: INT _ 0] RETURNS [out: Viewer, loaded: BOOL _ FALSE] = { <> <> <> <> clearMessage: BOOL _ TRUE; 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: ROPE _ NIL; 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; <> ViewerPrivate.ReleaseWriteLocks[viewer, oldViewer, linkViewer]; ENDLOOP; { ENABLE UNWIND => ViewerPrivate.ReleaseWriteLocks[viewer, oldViewer, linkViewer]; [] _ TEditDocument.SpinAndLock[tdd, "DoLoad"]; <> 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; <> ENDCASE; EXITS oldCase => {}; }; SELECT TRUE FROM oldViewer = NIL => { <> 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 => { <> 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: ROPE _ NIL] 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] = { <> <> mapList: VersionMap.MapList ~ VersionMapDefaults.GetMapList[$Source]; ranges: VersionMap.RangeList ~ VersionMap.ShortNameToRanges[mapList, shortName]; bestName: ROPE _ NIL; 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: BOOL _ FALSE, search: ROPE _ NIL] = { dot: INT _ 0; hasExtension, standardExtension, simpleName: BOOL _ FALSE; base, ext: ROPE _ NIL; proc: FileNameProc ~ fileNameProc; Report["Directory lookup for ", sel]; <> 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: ROPE _ NIL] = { cp: FS.ComponentPositions; simpleName: BOOL ~ sel.SkipTo[skip: "[]<>/"]=sel.Size[]; xname, base, ext, impl: ROPE _ NIL; [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: ROPE _ NIL; 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: ROPE _ NIL] RETURNS [ROPE] = { fname: ROPE _ NIL; 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: BOOL _ FALSE; search: ROPE _ NIL; 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]; }; <<>> <> <> <> <> <> <> <> <> <<[] _ DoLoadFile[viewer, selection, FALSE]>> <<[] _ DoOpenFile[selection, viewer]>> <<[] _ DoCloseAndOpenFile[viewer, selection]>> <> <<[] _ DoLoadImplFile[viewer, selection, FALSE]>> <<[] _ DoOpenImplFile[selection, viewer]>> <<[] _ DoCloseAndOpenImplFile[viewer, selection]>> <> <> <> <> <> <> <> <> <<>> <> <> <> <<[] _ DoOpenFile[NIL, selection]>> <<>> <> <> <> <<>> DoEmptyViewer: PROC [parent: Viewer, wDir: ROPE _ NIL] = { 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: ROPE _ NIL, close: BOOL _ FALSE, 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: ROPE _ NIL, 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: ROPE _ NIL] 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: ROPE _ NIL, close: BOOL _ FALSE] 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: ROPE _ NIL, 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: ROPE _ NIL] 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"]; <> 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"]; <> 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 [ROPE _ NIL] = { 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: BOOL _ FALSE] = { [] _ FS.FileInfo[name ! FS.Error => { new _ TRUE; CONTINUE }]; }; PreStore: PUBLIC Menus.MenuProc = { -- called when unguarding Store sel: ROPE _ ViewerTools.GetSelectionContents[]; fileName: ROPE _ NIL; new: BOOL _ FALSE; 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: ROPE _ NIL] = { 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 { <> 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.