<<>> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <<>> DIRECTORY Ascii, BridgeComm, BridgeDriver, BridgeExec, BridgeFTPOps, Convert, IO, Menus, MessageWindow, NetworkStream, NodeProps, Rope, TEditSplit, TextLooks, TextEdit, TextNode, Tioga, TiogaIO, TiogaMenuOps, TiogaOps, UserProfile, ViewerClasses, ViewerEvents, ViewerForkers, ViewerGroupLocks, ViewerOps, ViewerTools ; BridgeREditImpl: CEDAR PROGRAM IMPORTS Ascii, BridgeComm, BridgeDriver, BridgeExec, BridgeFTPOps, Convert, IO, Menus, MessageWindow, NetworkStream, NodeProps, Rope, TEditSplit, TextEdit, TiogaIO, TiogaMenuOps, TiogaOps, UserProfile, ViewerForkers, ViewerOps, ViewerTools, ViewerEvents, ViewerGroupLocks ~ { <> ROPE: TYPE ~ Rope.ROPE; NetworkStreamPair: TYPE ~ BridgeExec.NetworkStreamPair; Handle: TYPE ~ REF HandleRep ¬ NIL; HandleRep: TYPE ~ RECORD [ nsp: NetworkStreamPair, args: ROPE, remoteName: ROPE, overwrite: BOOL, session: BridgeExec.Session, viewer: ViewerClasses.Viewer, pendingName: ROPE, workingDirectory: ROPE, clientData: REF ]; <> commandName: ROPE ~ "REdit"; handleProperty: ATOM ~ $REditHandle; overwriteKey: ROPE ¬ "Bridge.REdit.overwrite"; overwriteDefault: BOOL ¬ FALSE; <> HasPWD: PROC[nsp: NetworkStreamPair] RETURNS [hasIt: BOOL ¬ FALSE] ~ { <> <> ansMsg: CHAR; ansArg: ROPE; <<>> [ansMsg, ansArg] ¬ BridgeComm.PutMsgWithReply[nsp~nsp, msg~'X, arg~"D"]; IF ansMsg = 'Y THEN hasIt ¬ TRUE; }; GetCurDir: PROC[nsp: NetworkStreamPair] RETURNS [curDir: ROPE ¬ NIL] ~ { <> <> ENABLE BridgeComm.Error => CONTINUE; ansMsg: CHAR; ansArg: ROPE; list: LIST OF ROPE; IF NOT HasPWD[nsp] THEN RETURN; [ansMsg, ansArg] ¬ BridgeComm.PutMsgWithReply[nsp~nsp, msg~'D, arg~NIL]; IF ansMsg # 'Y THEN RETURN; list ¬ BridgeDriver.ListOfRopeFromCmd[ansArg]; curDir ¬ list.first; }; SetCurDir: PROC[nsp: NetworkStreamPair, newDir: ROPE] RETURNS [curDir: ROPE ¬ NIL] ~ { <> <> ENABLE BridgeComm.Error => CONTINUE; ansMsg: CHAR; ansArg: Rope.ROPE; list: LIST OF ROPE; IF NOT HasPWD[nsp] THEN RETURN; [ansMsg, ansArg] ¬ BridgeComm.PutMsgWithReply[nsp~nsp, msg~'D, arg~newDir]; IF ansMsg # 'Y THEN RETURN; list ¬ BridgeDriver.ListOfRopeFromCmd[ansArg]; curDir ¬ list.first; }; <<>> Init: PROCEDURE [] RETURNS [] ~ { BridgeExec.Register[ name: commandName, createProc: Create, clientData: NIL, destroyProc: Destroy]; }; Create: BridgeExec.CreateProc ~ { <> <> <<-- stream for communication with host.>> <> <<-- as sent from host, with leading blanks stripped but no other processing performed. The command name is not included. By convention, args are terminated (rather than separated) by CRs.>> <> <<-- session in which this instance is being created.>> <> <<-- the clientData that was passed to AddCommand.>> <<]>> <> <> <<-- newly-created instance, or NIL.>> <<];>> argsList: LIST OF ROPE; remoteName: ROPE; handle: Handle; contentRope, excuse: ROPE; argsList ¬ BridgeDriver.ListOfRopeFromCmd[args]; remoteName ¬ IF argsList = NIL THEN NIL ELSE argsList.first; handle ¬ NEW[HandleRep ¬ [ nsp~nsp, args~args, remoteName~remoteName, overwrite~GetOverwriteMode[NIL], session~session, viewer~NIL, pendingName~NIL, workingDirectory~NIL, clientData: clientData]]; handle.workingDirectory ¬ GetCurDir[nsp: handle.nsp]; -- may be NIL IF Rope.IsEmpty[remoteName] THEN { contentRope ¬ ""; excuse ¬ NIL } ELSE { [result~contentRope, excuse~excuse] ¬ NamedFileToRope[handle.nsp, handle.remoteName]; }; IF Rope.IsEmpty[excuse] THEN { handle.viewer ¬ NewREditViewer[addWD: (handle.workingDirectory # NIL)]; ViewerOps.AddProp[viewer: handle.viewer, prop: handleProperty, val: handle]; handle.viewer ¬ RopeToViewer[viewer: handle.viewer, rope: contentRope, extensionRope: ExtensionFromFileName[remoteName]]; REditCaption[handle: handle]; ViewerOps.OpenIcon[icon: handle.viewer]; } ELSE { ExcuseMe[Rope.Concat["REdit couldn't open viewer: ", excuse]]; }; RETURN [instance: handle]; }; Destroy: BridgeExec.DestroyProc ~ { <> handle: Handle ¬ NARROW[instance]; IF handle.viewer = NIL THEN RETURN; FOR viewer: ViewerClasses.Viewer ¬ handle.viewer.link, viewer.link WHILE viewer # NIL DO { IF viewer = handle.viewer THEN EXIT; ViewerOps.DestroyViewer[viewer: viewer]; } ENDLOOP; ViewerOps.DestroyViewer[viewer: handle.viewer]; }; <> <> RopeToViewer: PROCEDURE [viewer: ViewerClasses.Viewer, rope: ROPE, extensionRope: ROPE ¬ NIL] RETURNS [result: ViewerClasses.Viewer] ~ { root: Tioga.Node ~ TiogaIO.FromRope[rope: rope]; AddFileExtensionProp[root, extensionRope]; viewer.class.set[viewer, root, TRUE, $TiogaDocument]; RETURN [result: viewer]; }; NamedFileToRope: PROC [nsp: NetworkStreamPair, remoteName: ROPE] RETURNS [result: ROPE ¬ NIL, success: BOOLEAN ¬ TRUE, excuse: ROPE ¬ NIL] ~ { ENABLE BridgeComm.Error => { result ¬ NIL; excuse ¬ msg; success ¬ FALSE; GOTO Done}; actionCalled: BOOLEAN ¬ FALSE; unwound: BOOLEAN ¬ FALSE; StreamToRopeActionProc: BridgeFTPOps.ActionProc ~ { <<[nsp: NetworkStreamPair, bytesReported: INT] RETURNS [quit: BOOLEAN _ FALSE]>> < something happened to the stream.>> ENABLE UNWIND => unwound ¬ TRUE; actionCalled ¬ TRUE; result ¬ RopeFromStream[nsp: nsp, bytes: bytesReported]; RETURN [quit: FALSE]; }; IF Rope.IsEmpty[remoteName] THEN RETURN [success: FALSE, excuse: "Null remote name"]; BridgeComm.PutMsgWithAck[nsp: nsp, msg: 'X, arg: "x"]; -- don't translate CR-LF excuse ¬ BridgeFTPOps.RetrieveStream[ nsp: nsp, remoteName: remoteName, action: StreamToRopeActionProc]; IF unwound AND excuse = NIL THEN excuse ¬ "something happened to the stream."; IF NOT actionCalled AND excuse = NIL THEN result ¬ NIL; IF excuse # NIL THEN success ¬ FALSE; RETURN [result: result, success: success, excuse: excuse]; EXITS Done => NULL; }; NamedFileToViewer: PROCEDURE [ nsp: NetworkStreamPair, viewer: ViewerClasses.Viewer, remoteName: ROPE] RETURNS [ result: ViewerClasses.Viewer ¬ NIL, success: BOOLEAN ¬ TRUE, excuse: ROPE ¬ NIL] ~ { r: ROPE; [r, success, excuse] ¬ NamedFileToRope[nsp, remoteName]; IF success THEN result ¬ RopeToViewer[viewer~viewer, rope~r, extensionRope~ExtensionFromFileName[remoteName]]; }; RopeFromStream: PROCEDURE [nsp: NetworkStreamPair, bytes: INT] RETURNS [rope: ROPE ¬ NIL] ~ { rope ¬ BridgeComm.GetRopeToEOM[nsp]; }; NewREditViewer: PROCEDURE [addWD: BOOL] RETURNS [viewer: ViewerClasses.Viewer ¬ NIL] ~ { viewer ¬ ViewerOps.CreateViewer[flavor: $Text]; viewer ¬ REditMenu[viewer: viewer, addWD: addWD]; }; REditCaption: PROCEDURE [handle: Handle] RETURNS [] ~ { sessionName: ROPE ~ BridgeExec.SessionNameFromSession[session: handle.session]; transportClass: ATOM ~ NetworkStream.GetStreamInfo[handle.nsp.in].protocolFamily; dirRope: ROPE ~ IF handle.workingDirectory # NIL THEN Rope.Concat[" WD = ", handle.workingDirectory] ELSE NIL; viewerName: ROPE; <> ChangeCaption: PROCEDURE [] RETURNS [] ~ { FOR split: ViewerClasses.Viewer ¬ handle.viewer, split.link WHILE split # NIL DO { split.name ¬ viewerName; <> ViewerOps.PaintViewer[viewer: split, hint: ViewerClasses.PaintHint.caption]; IF split.link = handle.viewer THEN EXIT; } ENDLOOP; }; viewerName ¬ Rope.Cat[handle.remoteName, " (", sessionName, ")"]; ViewerGroupLocks.CallRootAndLinksUnderWriteLock[ChangeCaption, handle.viewer]; }; <> LineFeedRope: ROPE ~ "\l"; SetNewLine: PROC [root: Tioga.Node] ~ { IF NodeProps.GetProp[root, $NewlineDelimiter] = NIL THEN NodeProps.PutProp[root, $NewlineDelimiter, LineFeedRope]; }; SetOverwriteMode: PROC [handle: Handle, overwrite: BOOL] ~ { IF handle # NIL THEN handle.overwrite ¬ overwrite; }; GetOverwriteMode: PROC [handle: Handle] RETURNS [overwrite: BOOL] ~ { IF handle # NIL THEN overwrite ¬ handle.overwrite ELSE overwrite ¬ UserProfile.Boolean[key~overwriteKey, default~overwriteDefault]; }; RopeFromViewer: PROC [viewer: ViewerClasses.Viewer] RETURNS [rope: ROPE] ~ { isTioga: BOOL; [isTioga, rope] ¬ IsThisViewerTioga[viewer]; IF isTioga THEN { root: Tioga.Node ~ TiogaOps.ViewerDoc[viewer]; SetNewLine[root]; rope ¬ TiogaIO.ToRope[root]; }; }; ViewerToNamedFile: PROCEDURE [ nsp: NetworkStreamPair, remoteName: ROPE, overwrite: BOOL, viewer: ViewerClasses.Viewer] RETURNS [ success: BOOLEAN ¬ TRUE, excuse: ROPE ¬ NIL] ~ { ENABLE BridgeComm.Error => { excuse ¬ msg; success ¬ FALSE; CONTINUE }; unwound: BOOLEAN ¬ FALSE; actionCalled: BOOLEAN ¬ FALSE; ropeFromViewer: ROPE; RopeToStreamActionProc: BridgeFTPOps.ActionProc ~ { <> < something happened to the stream.>> ENABLE UNWIND => unwound ¬ TRUE; success: BOOLEAN ¬ TRUE; actionCalled ¬ TRUE; success ¬ RopeToStream[nsp~nsp, rope~ropeFromViewer]; RETURN [quit: NOT success]; }; IF Rope.IsEmpty[remoteName] THEN RETURN [success~FALSE, excuse~"null remote name"]; ropeFromViewer ¬ RopeFromViewer[viewer]; BridgeComm.PutMsgWithAck[nsp~nsp, msg~'X, arg~"x"];-- don`t translate CR-LF excuse ¬ BridgeFTPOps.StoreStream[nsp, remoteName, overwrite, RopeToStreamActionProc]; IF unwound AND excuse = NIL THEN { excuse ¬ "communication failure."; }; IF NOT actionCalled AND excuse = NIL THEN { excuse ¬ "never asked to write."; }; IF excuse # NIL THEN { success ¬ FALSE; }; RETURN [success: success, excuse: excuse]; }; RopeToStream: PROCEDURE [ nsp: NetworkStreamPair, rope: ROPE] RETURNS [success: BOOLEAN ¬ TRUE] ~ { IO.PutRope[self: nsp.out, r: rope]; RETURN [success: TRUE]; }; <> REditMenu: PROCEDURE [viewer: ViewerClasses.Viewer, addWD: BOOL] RETURNS [result: ViewerClasses.Viewer ¬ NIL] ~ { menu: Menus.Menu ¬ Menus.CreateMenu[]; CopyEntry: PROC [name: Rope.ROPE] = { <<--Copies a menu entry from the tioga menu>> old: Menus.MenuEntry ~ Menus.FindEntry[menu: TiogaMenuOps.tiogaMenu, entryName: name]; IF old#NIL THEN { Menus.AppendMenuEntry[menu: menu, entry: Menus.CopyEntry[old], line: 0]; }; }; Menus.AppendMenuEntry[ menu: menu, entry: Menus.CreateEntry[ name: "Clear", proc: ClearProc]]; Menus.AppendMenuEntry[ menu: menu, entry: Menus.CreateEntry[ name: "Reset", proc: ResetProc, guarded: TRUE, documentation: preResetProc]]; Menus.AppendMenuEntry[ menu: menu, entry: Menus.CreateEntry[ name: "Get", proc: GetProc]]; Menus.AppendMenuEntry[ menu: menu, entry: Menus.CreateEntry[ name: "RemoteStore", proc: RemoteStoreProc, guarded: TRUE, documentation: preRemoteStoreProc]]; Menus.AppendMenuEntry[ menu: menu, entry: Menus.CreateEntry[ name: "RemoteSave", proc: RemoteSaveProc]]; Menus.AppendMenuEntry[ menu: menu, entry: Menus.CreateEntry[name: "Split", proc: SplitProc], line: 0]; CopyEntry["Places"]; CopyEntry["Levels"]; CopyEntry["Line"]; IF addWD THEN Menus.AppendMenuEntry[ menu: menu, entry: Menus.CreateEntry[name: "WD", proc: DirectoryProc], line: 0]; CopyEntry["Plain"]; CopyEntry["Cedar"]; [] ¬ ViewerOps.SetMenu[viewer: viewer, menu: menu]; [] ¬ TiogaMenuOps.DefaultMenus[viewer: viewer]; [] ¬ ViewerEvents.RegisterEventProc[ proc: ViewerEventDestroyProc, event: ViewerEvents.ViewerEvent.destroy, filter: viewer, before: TRUE ]; RETURN [result: viewer]; }; ViewerEventDestroyProc: ViewerEvents.EventProc ~ { <> handle: Handle ¬ NARROW[ViewerOps.FetchProp[viewer: viewer, prop: handleProperty]]; IF event # destroy OR before # TRUE THEN ERROR; IF handle # NIL THEN { IF viewer.link = NIL OR viewer.link = viewer THEN { BridgeExec.DestroyInstance[handle.session, handle]; } ELSE { handle.viewer ¬ viewer.link; }; }; RETURN [abort: FALSE]; }; ClearProc: Menus.ClickProc ~ { <> handle: Handle ~ NARROW[ViewerOps.FetchProp[viewer: parent, prop: handleProperty]]; SELECT mouseButton FROM Menus.MouseButton.red => { IF (IF ViewerOpsGetNewVersion[viewer: parent] THEN NOT handle.remoteName.Equal[NIL] ELSE FALSE) THEN { UnsavedEdits[]; } ELSE { WHILE parent.link # NIL AND parent.link # parent DO { ViewerOps.DestroyViewer[viewer: parent.link]; } ENDLOOP; [] ¬ RopeToViewer[viewer: parent, rope: NIL]; handle.remoteName ¬ NIL; }; }; Menus.MouseButton.yellow, Menus.MouseButton.blue => { handle: Handle ~ NARROW[ViewerOps.FetchProp[ viewer: parent, prop: handleProperty]]; excuse: ROPE ¬ NIL; excuse ¬ BridgeDriver.StartSession[ sessionName~BridgeExec.SessionNameFromSession[session: handle.session], nameAndPasswordProc~NIL, cmd~BridgeDriver.CmdFromListOfRope[LIST[commandName]] ]; IF NOT excuse.IsEmpty[] THEN { ExcuseMe[excuse]; }; IF mouseButton = Menus.MouseButton.blue THEN { ViewerOps.CloseViewer[viewer: parent]; }; }; ENDCASE => ERROR; REditCaption[handle: handle]; }; preResetProc: REF Menus.ClickProc ~ NEW[Menus.ClickProc ¬ PreResetProc]; PreResetProc: Menus.ClickProc ~ { <> <> <> <> <> handle: Handle ~ NARROW[ViewerOps.FetchProp[viewer: parent, prop: handleProperty]]; MessageWindow.Append[message: "Confirm reset store of ", clearFirst: TRUE]; MessageWindow.Append[message: handle.remoteName, clearFirst: FALSE]; }; ResetProc: Menus.ClickProc ~ { <> handle: Handle ~ NARROW[ViewerOps.FetchProp[viewer: parent, prop: handleProperty]]; success: BOOLEAN ¬ FALSE; excuse: ROPE ¬ NIL; WHILE parent.link # NIL AND parent.link # parent DO { ViewerOps.DestroyViewer[viewer: parent.link]; } ENDLOOP; [success: success, excuse: excuse] ¬ NamedFileToViewer[ nsp: handle.nsp, viewer: parent, remoteName: handle.remoteName]; IF success THEN { <> MessageWindow.Clear[]; parent ¬ ViewerOpsSetNewVersion[viewer: parent, newVersion: FALSE]; } ELSE { ExcuseMe[IO.PutFR["Reset of %g failed: %g", IO.rope[handle.remoteName], IO.rope[excuse]]]; }; }; FileNameFromOldAndNewNames: PROC [old: ROPE, new: ROPE] RETURNS [shortName: ROPE] ~ { slashPos: INT; IF Rope.IsEmpty[new] THEN RETURN [NIL]; IF Rope.IsEmpty[old] THEN RETURN [new]; IF Rope.Find[new, "/"] >= 0 THEN RETURN [new]; IF (slashPos ¬ Rope.FindBackward[old, "/"]) < 0 THEN RETURN [new]; RETURN [Rope.Concat[Rope.Substr[old, 0, 1+slashPos], new]]; }; GetProc: Menus.ClickProc ~ { <> excuse: ROPE ¬ NIL; { fileName: ROPE ~ ViewerTools.GetSelectionContents[]; handle: Handle ~ NARROW[ViewerOps.FetchProp[viewer: parent, prop: handleProperty]]; success: BOOLEAN ¬ TRUE; fileNameToSend: ROPE; IF Rope.IsEmpty[fileName] THEN { excuse ¬ "null file name"; GOTO NotGood }; fileNameToSend ¬ FileNameFromOldAndNewNames[handle.remoteName, fileName ]; SELECT mouseButton FROM Menus.MouseButton.red => { IF (IF ViewerOpsGetNewVersion[viewer: parent] THEN NOT handle.remoteName.Equal[NIL] ELSE FALSE) THEN { UnsavedEdits[]; } ELSE { WHILE parent.link # NIL AND parent.link # parent DO { ViewerOps.DestroyViewer[viewer: parent.link]; } ENDLOOP; [excuse: excuse, success: success] ¬ NamedFileToViewer[ nsp: handle.nsp, viewer: parent, remoteName: fileNameToSend]; IF NOT success THEN GOTO NotGood; handle.remoteName ¬ fileNameToSend; REditCaption[handle: handle]; }; }; Menus.MouseButton.yellow, Menus.MouseButton.blue => { cmd: ROPE; IF Rope.Fetch[fileNameToSend, 0] # '/ THEN fileNameToSend ¬ Rope.Cat[handle.workingDirectory, "/", fileNameToSend]; cmd ¬ BridgeDriver.CmdFromListOfRope[ LIST[commandName, fileNameToSend] ]; excuse ¬ BridgeDriver.StartSession[ sessionName: BridgeExec.SessionNameFromSession[session: handle.session], nameAndPasswordProc: NIL, cmd: cmd]; IF excuse # NIL THEN GOTO NotGood; IF mouseButton = Menus.MouseButton.blue THEN { ViewerOps.CloseViewer[viewer: parent]; }; }; ENDCASE => ERROR; EXITS NotGood => ExcuseMe[excuse: excuse]; }}; preRemoteStoreProc: REF Menus.ClickProc ~ NEW[Menus.ClickProc ¬ PreRemoteStoreProc]; PreRemoteStoreProc: Menus.ClickProc ~ { <> <> <> <> <> handle: Handle ~ NARROW[ViewerOps.FetchProp[viewer: parent, prop: handleProperty]]; handle.pendingName ¬ ViewerTools.GetSelectionContents[]; MessageWindow.Append[message: "Confirm remote store of ", clearFirst: TRUE]; MessageWindow.Append[message: handle.pendingName, clearFirst: FALSE]; }; RemoteStoreProc: Menus.ClickProc ~ { <> handle: Handle ~ NARROW[ViewerOps.FetchProp[viewer: parent, prop: handleProperty]]; success: BOOLEAN ¬ FALSE; excuse: ROPE ¬ NIL; [success: success, excuse: excuse] ¬ ViewerToNamedFile[ nsp: handle.nsp, remoteName: handle.pendingName, overwrite: GetOverwriteMode[handle], viewer: parent]; IF success THEN { <> MessageWindow.Clear[]; handle.remoteName ¬ handle.pendingName; handle.pendingName ¬ NIL; REditCaption[handle]; parent ¬ ViewerOpsSetNewVersion[viewer: parent, newVersion: FALSE]; } ELSE { ExcuseMe[IO.PutFR["Remote store of %g failed: %g", IO.rope[handle.pendingName], IO.rope[excuse]]]; }; }; RemoteSaveProc: Menus.ClickProc ~ { <> handle: Handle ~ NARROW[ViewerOps.FetchProp[viewer: parent, prop: handleProperty]]; success: BOOLEAN ¬ FALSE; excuse: ROPE ¬ NIL; [success: success, excuse: excuse] ¬ ViewerToNamedFile[ nsp: handle.nsp, remoteName: handle.remoteName, overwrite: GetOverwriteMode[handle], viewer: parent]; IF success THEN { parent ¬ ViewerOpsSetNewVersion[viewer: parent, newVersion: FALSE]; } ELSE { ExcuseMe[IO.PutFR["Remote save of %g failed: %g", IO.rope[handle.remoteName], IO.rope[excuse]]]; }; }; SplitProc: Menus.ClickProc ~ { <> handle: Handle ~ NARROW[ViewerOps.FetchProp[viewer: parent, prop: handleProperty]]; CopyHandleProp: PROCEDURE [] RETURNS [] ~ { FOR split: ViewerClasses.Viewer ¬ parent.link, split.link WHILE split # NIL DO { IF split = parent THEN EXIT; ViewerOps.AddProp[viewer: split, prop: handleProperty, val: handle]; } ENDLOOP; }; TEditSplit.Split[viewer: parent]; ViewerGroupLocks.CallRootAndLinksUnderWriteLock[ proc: CopyHandleProp, viewer: parent]; }; DirectoryProc: Menus.ClickProc ~ { <> handle: Handle ~ NARROW[ViewerOps.FetchProp[viewer: parent, prop: handleProperty]]; newWDir: ROPE; SELECT mouseButton FROM red => { selectionRope: ROPE ~ ViewerTools.GetSelectionContents[]; newWDir ¬ SetCurDir[handle.nsp, selectionRope]; IF newWDir # NIL THEN { handle.workingDirectory ¬ newWDir; REditCaption[handle]; } ELSE { ExcuseMe[IO.PutFR1["Can't set WD to %g", IO.rope[newWDir]]]; }; }; yellow => { newWDir ¬ GetCurDir[nsp: handle.nsp]; IF newWDir # NIL THEN { handle.workingDirectory ¬ newWDir; MessageWindow.Append[message: "Working Directory is ", clearFirst: TRUE]; MessageWindow.Append[message: handle.workingDirectory, clearFirst: FALSE]; REditCaption[handle]; } ELSE { ExcuseMe["Can't get WD"]; }; }; blue => RETURN; ENDCASE => ERROR; }; <> IsThisViewerTioga: PROC [viewer: ViewerClasses.Viewer] RETURNS [isTioga: BOOL, plainRope: ROPE] ~ { root: Tioga.Node ~ TiogaOps.ViewerDoc[viewer]; rope: ROPE ~ TiogaIO.RopeFromSimpleDoc[root]; RETURN[isTioga: rope=NIL, plainRope: rope]; }; ViewerOpsGetNewVersion: PROCEDURE [viewer: ViewerClasses.Viewer] RETURNS [newVersion: BOOLEAN ¬ FALSE] ~ { <> RETURN [newVersion: viewer.newVersion]; }; ViewerOpsSetNewVersion: PUBLIC PROCEDURE [ viewer: ViewerClasses.Viewer, newVersion: BOOLEAN ¬ TRUE] RETURNS [set: ViewerClasses.Viewer ¬ NIL] ~ { <> <> <> <<-- FOR v: Viewer _ viewer, v.link WHILE v # NIL DO>> <<-- IF ViewerEvents.ProcessEvent[edit, v, TRUE].abort THEN RETURN;>> <<-- IF v.link = viewer THEN EXIT;>> <<-- ENDLOOP;>> FOR v: ViewerClasses.Viewer ¬ viewer, v.link WHILE v # NIL DO { v.newVersion ¬ newVersion; ViewerForkers.ForkPaint[ viewer: v, hint: ViewerClasses.PaintHint.caption, tryShortCuts: TRUE]; <<-- [] _ ViewerEvents.ProcessEvent[edit, v, FALSE];>> IF v.link = viewer THEN EXIT; } ENDLOOP; RETURN [set: viewer]; }; <> LowerCaseRopeFromRope: PROC [rope: ROPE] RETURNS [lowerCaseRope: ROPE] ~ { <> ToLower: Rope.TranslatorType -- [old: CHAR] RETURNS [CHAR] -- ~ { RETURN [Ascii.Lower[old]]; }; lowerCaseRope ¬ Rope.Translate[base~rope, translator~ToLower]; }; ExtensionFromFileName: PROC [fileName: ROPE] RETURNS [extensionRope: ROPE] ~ { dotPos: INT; IF Rope.IsEmpty[fileName] THEN RETURN [NIL]; IF (dotPos ¬ Rope.FindBackward[fileName, "."]) < 0 THEN RETURN [NIL]; RETURN [LowerCaseRopeFromRope[Rope.Substr[fileName, 1+dotPos]]]; }; AddFileExtensionProp: PROC [root: TextNode.Ref, extensionRope: ROPE] ~ { extensionAtom: ATOM ~ IF Rope.IsEmpty[extensionRope] THEN $null ELSE Convert.AtomFromRope[extensionRope]; TextEdit.PutProp[node: root, name: $FileExtension, value: extensionAtom]; }; <> ExcuseMe: PROCEDURE [excuse: ROPE] RETURNS [] ~ { MessageWindow.Append[message: excuse, clearFirst: TRUE]; MessageWindow.Blink[]; }; UnsavedEdits: PROCEDURE [] RETURNS [] ~ { ExcuseMe["DANGER: that viewer contains unsaved edits."]; }; <> Init[]; }.