BridgeREditImpl.mesa
Copyright Ó 1987, 1988, 1989, 1990, 1991, 1992, 1993 by Xerox Corporation. All rights reserved.
Peter B. Kessler, February 29, 1988 5:55:38 pm PST
Bill Jackson (bj) October 28, 1987 11:24:15 pm PST
Bloomenthal, November 25, 1987 3:02:11 pm PST
Eduardo Pelegri-Llopart March 9, 1989 8:51:58 am PST
Demers, October 14, 1990 11:00 pm PDT
Tim Diebert: September 13, 1989 8:31:59 am PDT
Chauser, April 10, 1991 9:40 am PDT
Willie-s, January 29, 1992 6:46 pm PST
Michael Plass, March 10, 1992 4:22 pm PST
Doug Wyatt, April 14, 1992 5:07 pm PDT
Christian Jacobi, March 19, 1993 12:17 pm PST
An implementation of a Bridge REdit server using Tioga as the editor.
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
~ {
Types
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
];
Constants
commandName: ROPE ~ "REdit";
handleProperty: ATOM ~ $REditHandle;
overwriteKey: ROPE ¬ "Bridge.REdit.overwrite";
overwriteDefault: BOOL ¬ FALSE;
Bridge Procedures
HasPWD: PROC[nsp: NetworkStreamPair] RETURNS [hasIt: BOOL ¬ FALSE] ~ {
Queries C-bridge on its ability to set/query working directory.
May raise BridgeComm.Error
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] ~ {
Get the current directory associated with a control stream
Check whether the C-bridge supports it
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] ~ {
Set the current directory associated with a control stream
Check whether the C-bridge supports it
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 ~ {
PROCEDURE [
nsp: BridgeExec.NetworkStreamPair,
-- stream for communication with host.
args: ROPE,
-- 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: BridgeExec.Session,
-- session in which this instance is being created.
clientData: REF
-- the clientData that was passed to AddCommand.
]
RETURNS [
instance: BridgeExec.Instance
-- 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 ~ {
PROCEDURE [instance: Instance, clientData: REF]
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];
};
REdit Procedures.
Named file to 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: BOOLEANFALSE]
! UNWIND => 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;
viewerLabel: ROPE ~ Rope.Cat[sessionName, ": ", handle.remoteName];
ChangeCaption: PROCEDURE [] RETURNS [] ~ {
FOR split: ViewerClasses.Viewer ¬ handle.viewer, split.link WHILE split # NIL DO {
split.name ¬ viewerName;
split.label ← viewerLabel;
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];
};
Viewer to named file.
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 ~ {
PROCEDURE [nsp: NetworkStreamPair, bytesReported: INT, clientData: REF] RETURNS [quit: BOOLEANFALSE]
! UNWIND => 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];
};
Menu Procedures.
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 ~ {
PROC [viewer: Viewer, event: ViewerEvent, before: BOOL]
RETURNS[abort: BOOLFALSE]
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 ~ {
PROC [
parent: ViewerClasses.Viewer,
clientData: REF ANYNIL,
mouseButton: MouseButton ← red,
shift, control: BOOLFALSE]
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 ~ {
PROC [
parent: ViewerClasses.Viewer,
clientData: REF ANYNIL,
mouseButton: MouseButton ← red,
shift, control: BOOLFALSE]
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 ~ {
PROC [
parent: ViewerClasses.Viewer,
clientData: REF ANYNIL,
mouseButton: MouseButton ← red,
shift, control: BOOLFALSE]
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 {
Clear the confirm message and the [Edited] flag.
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 ~ {
PROC [
parent: ViewerClasses.Viewer,
clientData: REF ANYNIL,
mouseButton: MouseButton ← red,
shift, control: BOOLFALSE]
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 ~ {
PROC [
parent: ViewerClasses.Viewer,
clientData: REF ANYNIL,
mouseButton: MouseButton ← red,
shift, control: BOOLFALSE]
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 ~ {
PROC [
parent: ViewerClasses.Viewer,
clientData: REF ANYNIL,
mouseButton: MouseButton ← red,
shift, control: BOOLFALSE]
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 {
Clear the confirm message and the [Edited] flag.
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 ~ {
PROC [
parent: ViewerClasses.Viewer,
clientData: REF ANYNIL,
mouseButton: MouseButton ← red,
shift, control: BOOLFALSE]
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 ~ {
PROC [
parent: ViewerClasses.Viewer,
clientData: REF ANYNIL,
mouseButton: MouseButton ← red,
shift, control: BOOLFALSE]
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 ~ {
PROC [
parent: ViewerClasses.Viewer,
clientData: REF ANYNIL,
mouseButton: MouseButton ← red,
shift, control: BOOLFALSE]
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;
};
Tioga-ness checking ...
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] ~ {
Just a procedural accessor.
RETURN [newVersion: viewer.newVersion];
};
ViewerOpsSetNewVersion: PUBLIC PROCEDURE [
viewer: ViewerClasses.Viewer,
newVersion: BOOLEAN ¬ TRUE]
RETURNS [set: ViewerClasses.Viewer ¬ NIL] ~ {
This is just like ViewerOps.SetNewVersion if you call it with only the viewer argument.
If you call it with the newVersion ~ FALSE, then this unsets the newVersion bit in the viewer, and in all the viewers that are splits from this viewer.
This is grotty. The code is stolen from ViewerOpsImplB.IndicateNewVersion, except that I can't call ViewerEvents.ProcessEvent[edit, v, FALSE] to see if any of them abort, since ViewerEvents.ProcessEvent PRIVATE.
-- 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];
};
File Name Extension / Style
LowerCaseRopeFromRope: PROC [rope: ROPE] RETURNS [lowerCaseRope: ROPE] ~ {
Too bad this isn't in some public interface...
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];
};
Messages
ExcuseMe: PROCEDURE [excuse: ROPE] RETURNS [] ~ {
MessageWindow.Append[message: excuse, clearFirst: TRUE];
MessageWindow.Blink[];
};
UnsavedEdits: PROCEDURE [] RETURNS [] ~ {
ExcuseMe["DANGER: that viewer contains unsaved edits."];
};
Initialization.
Init[];
}.