TiogaDisplays:
CEDAR
PROGRAM
IMPORTS Ascii, Atom, Basics, CharDisplays, IO, IOClasses, InputFocus, Rope, Menus, MessageWindow, TiogaMenuOps, TiogaOps, TIPUser, ViewerOps
= {OPEN CharDisplays;
REFTEXT: TYPE = REF TEXT;
Viewer: TYPE = ViewerClasses.Viewer;
ViewerClass: TYPE = ViewerClasses.ViewerClass;
TiogaDisplay: TYPE = REF TiogaDisplayRep;
TiogaDisplayRep:
TYPE =
RECORD [
root, logNode, arrayNode: TiogaOps.Ref,
eatChars, spitChars: IO.STREAM ← NIL,
looks: ROPE ← "f",
spaces: ROPE ← NIL,
lineS: SEQUENCE length: NAT OF Line
];
Line:
TYPE =
RECORD [
tiogaCount: NAT,
key: ATOM];
tiogaClass, tiogaDisplayViewerClass: ViewerClass ← NIL;
tiogaDisplayViewerClassFlavor: ATOM ← $TiogaCharDisplay;
tdProp: ATOM ← $TiogaDisplayData;
tiogaCharDisplayClass: CharDisplayClass ←
NEW [CharDisplayClassRep ← [
name: "Tioga",
Init: TInit,
ChangeDetails: SimplyChange,
DeleteChar: DeleteChar,
TakeChar: TTakeChar,
CursorMove: TCursorMove,
Line: TLine,
ClearTo: ClearTo,
ClearAll: ClearAll,
SetEmph: SetEmph,
Emphasize: Emphasize,
SetFont: SetFont,
Flash: Flash]];
InitTiogaDisplayClass:
PROC = {
tdTIP: TIPUser.TIPTable ← TIPUser.InstantiateNewTIPTable["TiogaDisplay.TIP"];
menu: Menus.Menu ← Menus.CreateMenu[];
tiogaClass ← ViewerOps.FetchViewerClass[$Text];
tiogaDisplayViewerClass ← NEW [ViewerClasses.ViewerClassRec ← tiogaClass^];
tiogaDisplayViewerClass.notify ← NotifyTiogaDisplay;
tiogaDisplayViewerClass.icon ← typescript;
tdTIP.mouseTicks ← MIN[tdTIP.mouseTicks, tiogaClass.tipTable.mouseTicks];
tdTIP.opaque ← FALSE;
tdTIP.link ← tiogaClass.tipTable;
tiogaDisplayViewerClass.tipTable ← tdTIP;
menu.AppendMenuEntry[Menus.CreateEntry["Find", Find]];
menu.AppendMenuEntry[Menus.CreateEntry["Word", Word]];
menu.AppendMenuEntry[Menus.CreateEntry["Def", Def]];
menu.AppendMenuEntry[Menus.CreateEntry["Position", Position]];
menu.AppendMenuEntry[Menus.CreateEntry["Normalize", Normalize]];
menu.AppendMenuEntry[Menus.CreateEntry["PrevPlace", PrevPlace]];
menu.AppendMenuEntry[Menus.CreateEntry["Reselect", Reselect]];
tiogaDisplayViewerClass.menu ← menu;
ViewerOps.RegisterViewerClass[tiogaDisplayViewerClassFlavor, tiogaDisplayViewerClass];
RegClass[tiogaCharDisplayClass];
};
Find: Menus.MenuProc = {
v: Viewer ← NARROW[parent];
[] ← TiogaOps.FindText[viewer: v, whichDir: SearchDir[mouseButton], case: NOT shift];
};
Word: Menus.MenuProc = {
v: Viewer ← NARROW[parent];
[] ← TiogaOps.FindWord[viewer: v, whichDir: SearchDir[mouseButton], case: NOT shift];
};
Def: Menus.MenuProc = {
v: Viewer ← NARROW[parent];
[] ← TiogaOps.FindDef[viewer: v, whichDir: SearchDir[mouseButton], case: NOT shift];
};
SearchDir:
PROC [mb: Menus.MouseButton]
RETURNS [sd: TiogaOps.SearchDir] =
{sd ← SELECT mb FROM red => forwards, yellow => anywhere, blue => backwards, ENDCASE => ERROR};
Position: Menus.MenuProc = {
v: Viewer ← NARROW[parent];
TiogaMenuOps.Position[v]};
Normalize: Menus.MenuProc = {
v: Viewer ← NARROW[parent];
TiogaMenuOps.Normalize[v]};
PrevPlace: Menus.MenuProc = {
v: Viewer ← NARROW[parent];
TiogaMenuOps.PrevPlace[v]};
Reselect: Menus.MenuProc = {
v: Viewer ← NARROW[parent];
TiogaMenuOps.Reselect[v]};
NotifyTiogaDisplay:
PROC [self: Viewer, input:
LIST
OF
REF
ANY]
--ViewerClasses.NotifyProc-- = {
td: TiogaDisplay ← GetDisplay[self];
WITH input.first
SELECT
FROM
a:
ATOM =>
SELECT a
FROM
$TDInput => {
r: ROPE;
ctl, shift, meta: BOOL ← FALSE;
FOR input ← input.rest, input.rest
WHILE input #
NIL
DO
WITH input.first
SELECT
FROM
R: ROPE => r ← R;
t: REFTEXT => r ← Rope.FromRefText[t];
b:
ATOM =>
SELECT b
FROM
$Ctl => ctl ← TRUE;
$Shift => shift ← TRUE;
$Meta => meta ← TRUE;
ENDCASE => ERROR;
ENDCASE => ERROR;
ENDLOOP;
FOR i:
INT
IN [0 .. r.Length[])
DO
c: CHAR ← r.Fetch[i];
IF NOT shift THEN c ← Ascii.Lower[c];
IF ctl THEN c ← Control[c];
IF meta THEN c ← c + 128;
td.eatChars.PutChar[c];
ENDLOOP;
RETURN;
};
ENDCASE;
r:
ROPE => {
td.eatChars.PutRope[r];
RETURN;
};
ENDCASE;
tiogaClass.notify[self, input];
};
Control:
PROC [c:
CHAR]
RETURNS [cc:
CHAR] = {
d: NAT ← c - 0C;
cd: NAT ← Basics.BITAND[d, 31];
cc ← 0C + cd;
};
GetDisplay:
PROC [self: Viewer]
RETURNS [td: TiogaDisplay] =
INLINE
{td ← NARROW[ViewerOps.FetchProp[viewer: self, prop: tdProp]]};
TInit:
PROC [cd: CharDisplay, initData:
REF
ANY ←
NIL] = {
td: TiogaDisplay ← NEW [TiogaDisplayRep[cd.det.lines]];
InitDoc:
PROC [root: TiogaOps.Ref] = {
IF root # td.root THEN ERROR;
TiogaOps.SelectPoint[viewer: cd.viewer, caret: [td.arrayNode, 0]];
TiogaOps.Break[];
td.logNode ← TiogaOps.FirstChild[td.root];
td.arrayNode ← TiogaOps.LastChild[td.root];
TiogaOps.SelectPoint[viewer: cd.viewer, caret: [td.arrayNode, 0]];
TiogaOps.SetLooks["f", caret];
TiogaOps.InsertChar['\n];
FOR i:
NAT
IN [0 .. cd.det.columns)
DO
TiogaOps.InsertChar['0 + (i / 10)];
ENDLOOP;
TiogaOps.InsertChar['\n];
TiogaOps.SetLooks["fz", caret];
FOR i:
NAT
IN [0 .. cd.det.columns)
DO
TiogaOps.InsertChar['0 + (i MOD 10)];
ENDLOOP;
TiogaOps.SetLooks["f", caret];
TiogaOps.InsertChar['\n];
FOR l:
NAT
IN [0 .. cd.det.lines)
DO
TiogaOps.InsertChar['\n];
td.lineS[l] ← [
tiogaCount: 0,
key: Atom.MakeAtom[IO.PutFR["Before%g", IO.int[l]]]
];
ENDLOOP;
};
cd.otherInstanceData ← td;
cd.viewer ← ViewerOps.CreateViewer[flavor: tiogaDisplayViewerClassFlavor, info: [name: cd.name]];
cd.viewer.tipTable ← tiogaDisplayViewerClass.tipTable;
cd.viewer.menu ← tiogaDisplayViewerClass.menu.CopyMenu[];
ViewerOps.AddProp[viewer: cd.viewer, prop: tdProp, val: td];
td.root ← TiogaOps.ViewerDoc[cd.viewer];
td.logNode ← TiogaOps.FirstChild[td.root];
td.arrayNode ← TiogaOps.LastChild[td.root];
[push: td.eatChars, pull: td.spitChars] ← IOClasses.CreatePipe[];
cd.fromDisplay ← td.spitChars;
td.spaces ← " ";
FOR i:
INT
IN [0 .. cd.det.columns)
DO
td.spaces ← td.spaces.Cat[" "];
ENDLOOP;
TiogaOps.CallWithLocks[InitDoc, td.root];
InputFocus.SetInputFocus[cd.viewer];
SetLines[cd, td];
};
SetLines:
PROC [cd: CharDisplay, td: TiogaDisplay, recursed:
BOOL ←
FALSE] = {
IsBegin:
PROC [ci:
INT]
RETURNS [is:
BOOL] =
INLINE {is ← ci = 0 OR nr.Fetch[ci-1] = '\n};
nr: ROPE;
lineNum, prevCI: INT;
td.arrayNode ← TiogaOps.LastChild[td.root];
nr ← TiogaOps.GetRope[td.arrayNode];
lineNum ← cd.det.lines;
prevCI ← nr.Length[];
FOR ci:
INT ← nr.Length[]-1, ci-1
WHILE lineNum > 0
DO
IF ci < 0
THEN {
IF recursed THEN ERROR;
TiogaOps.SelectPoint[viewer: cd.viewer, caret: [td.arrayNode, 0]];
FOR i: INT IN [0 .. lineNum] DO TiogaOps.InsertChar['\n] ENDLOOP;
SetLines[cd, td, TRUE];
EXIT;
};
IF IsBegin[ci]
THEN {
lineNum ← lineNum - 1;
TiogaOps.PutTextKey[node: td.arrayNode, where: ci, key: td.lineS[lineNum].key];
td.lineS[lineNum].tiogaCount ← prevCI - ci - 1;
prevCI ← ci;
};
ENDLOOP;
};
DeleteChar:
PROC [cd: CharDisplay] = {
td: TiogaDisplay ← NARROW[cd.otherInstanceData];
base: INT ← TiogaOps.GetTextKey[node: td.arrayNode, key: td.lineS[cd.line].key].where;
WithLocks:
PROC [root: TiogaOps.Ref] = {
IF root # td.root THEN ERROR;
TiogaOps.SelectPoint[viewer: cd.viewer, caret: [td.arrayNode, base+cd.col]];
TiogaOps.DeleteNextCharacter[];
td.lineS[cd.line].tiogaCount ← td.lineS[cd.line].tiogaCount - 1;
};
IF cd.col >= td.lineS[cd.line].tiogaCount THEN RETURN;
TiogaOps.CallWithLocks[WithLocks, td.root];
};
TTakeChar:
PROC [cd: CharDisplay, char:
CHAR, insert:
BOOL ←
FALSE] = {
td: TiogaDisplay ← NARROW[cd.otherInstanceData];
base: INT ← TiogaOps.GetTextKey[node: td.arrayNode, key: td.lineS[cd.line].key].where;
wasEmpty: BOOL ← td.lineS[cd.line].tiogaCount = 0;
WithLocks:
PROC [root: TiogaOps.Ref] = {
IF root # td.root THEN ERROR;
TiogaOps.SelectPoint[viewer: cd.viewer, caret: [td.arrayNode, base+MIN[cd.col, td.lineS[cd.line].tiogaCount]]];
IF td.lineS[cd.line].tiogaCount < cd.col
THEN {
TiogaOps.SetLooks[looks: "f", which: caret];
WHILE td.lineS[cd.line].tiogaCount < cd.col
DO
TiogaOps.InsertChar[' ];
td.lineS[cd.line].tiogaCount ← td.lineS[cd.line].tiogaCount + 1;
ENDLOOP;
};
IF (
NOT insert)
AND td.lineS[cd.line].tiogaCount > cd.col
THEN {
TiogaOps.DeleteNextCharacter[];
td.lineS[cd.line].tiogaCount ← td.lineS[cd.line].tiogaCount - 1};
TiogaOps.SetLooks[looks: td.looks, which: caret];
TiogaOps.InsertChar[char];
td.lineS[cd.line].tiogaCount ← td.lineS[cd.line].tiogaCount + 1;
};
IF char = '\n THEN char ← char + 128;
TiogaOps.CallWithLocks[WithLocks, td.root];
IF wasEmpty OR cd.col = 0 THEN TiogaOps.PutTextKey[node: td.arrayNode, where: base, key: td.lineS[cd.line].key];
TCursorMove[cd, 0, 1, TRUE];
};
TCursorMove:
PROC [cd: CharDisplay, line, col:
INT, relative:
BOOL ←
FALSE, doLine, doCol:
BOOL ←
TRUE] = {
td: TiogaDisplay ← NARROW[cd.otherInstanceData];
WithLocks:
PROC [root: TiogaOps.Ref] = {
colDelta: INT;
IF root # td.root THEN ERROR;
IF line < cd.det.lines THEN NULL
ELSE IF NOT cd.det.scrolls THEN line ← line MOD cd.det.lines
ELSE {
delta: INT ← MIN[line - (cd.det.lines-1), cd.det.lines];
arrayEnd: INT ← TiogaOps.GetTextKey[node: td.arrayNode, key: td.lineS[cd.det.lines-1].key].where + td.lineS[cd.det.lines-1].tiogaCount + 1;
topStart: INT ← TiogaOps.GetTextKey[node: td.arrayNode, key: td.lineS[0].key].where;
topEnd: INT ← TiogaOps.GetTextKey[node: td.arrayNode, key: td.lineS[delta-1].key].where + td.lineS[delta-1].tiogaCount;
line ← line - delta;
TiogaOps.SelectPoint[viewer: cd.viewer, caret: [td.arrayNode, arrayEnd]];
FOR i: INT IN [0 .. delta) DO TiogaOps.InsertChar['\n] ENDLOOP;
FOR i:
INT
IN [delta .. cd.det.lines)
DO
td.lineS[i-delta].tiogaCount ← td.lineS[i].tiogaCount;
TiogaOps.PutTextKey[
node: td.arrayNode,
where: TiogaOps.GetTextKey[td.arrayNode, td.lineS[i].key].where,
key: td.lineS[i-delta].key];
ENDLOOP;
FOR i:
INT
IN [cd.det.lines - delta .. cd.det.lines)
DO
td.lineS[i].tiogaCount ← 0;
TiogaOps.PutTextKey[
node: td.arrayNode,
where: arrayEnd + i - (cd.det.lines - delta),
key: td.lineS[i].key];
ENDLOOP;
TiogaOps.SelectPoint[viewer: cd.viewer, caret: [td.logNode, TiogaOps.GetRope[td.logNode].Length[]]];
TiogaOps.SetSelection[viewer: cd.viewer, start: [td.arrayNode, topStart], end: [td.arrayNode, topEnd], pendingDelete: TRUE, which: secondary];
TiogaOps.ToPrimary[];
};
cd.line ← line;
cd.col ← col;
colDelta ← cd.col - td.lineS[cd.line].tiogaCount;
TiogaOps.SelectPoint[
viewer: cd.viewer,
caret: [
td.arrayNode,
TiogaOps.GetTextKey[node: td.arrayNode, key: td.lineS[cd.line].key].where + MIN[cd.col, td.lineS[cd.line].tiogaCount]]
];
IF colDelta > 0
THEN {
wasEmpty: BOOL ← td.lineS[cd.line].tiogaCount = 0;
TiogaOps.SetLooks["f", caret];
TiogaOps.InsertRope[td.spaces.Substr[0, colDelta]];
td.lineS[cd.line].tiogaCount ← td.lineS[cd.line].tiogaCount + colDelta;
IF wasEmpty THEN SetLines[cd, td];
};
};
IF relative THEN {line ← line + cd.line; col ← col + cd.col};
IF NOT doLine THEN line ← cd.line;
IF NOT doCol THEN col ← cd.col;
IF cd.det.autoMargins
THEN {
dl: INT ← col / cd.det.columns;
line ← line + dl;
col ← col - dl * cd.det.columns;
WHILE col < 0 DO col ← col + cd.det.columns; line ← line - 1 ENDLOOP;
}
ELSE col ← MAX[MIN[col, cd.det.columns-1], 0];
IF line < 0 THEN line ← 0;
IF cd.det.scrolls
AND line >= cd.det.lines
THEN {
TiogaOps.LockSel[primary];
TiogaOps.LockSel[secondary];
{
ENABLE
UNWIND =>
{TiogaOps.UnlockSel[secondary]; TiogaOps.UnlockSel[primary]};
TiogaOps.CallWithLocks[WithLocks, td.root];
};
TiogaOps.UnlockSel[secondary];
TiogaOps.UnlockSel[primary];
}
ELSE TiogaOps.CallWithLocks[WithLocks, td.root];
};
TLine:
PROC [cd: CharDisplay, insert:
BOOL] = {
td: TiogaDisplay ← NARROW[cd.otherInstanceData];
endBase: INT ← TiogaOps.GetTextKey[node: td.arrayNode, key: td.lineS[cd.det.lines-1].key].where;
endEnd: INT ← endBase + td.lineS[cd.det.lines-1].tiogaCount;
base: INT ← TiogaOps.GetTextKey[node: td.arrayNode, key: td.lineS[cd.line].key].where;
WithLocks:
PROC [root: TiogaOps.Ref] = {
IF root # td.root THEN ERROR;
IF insert
THEN {
TiogaOps.SetSelection[
viewer: cd.viewer,
start: [td.arrayNode, endBase],
end: [td.arrayNode, endEnd]];
TiogaOps.Delete[];
TiogaOps.SelectPoint[viewer: cd.viewer, caret: [td.arrayNode, base]];
TiogaOps.InsertChar['\n];
}
ELSE {
TiogaOps.SelectPoint[viewer: cd.viewer, caret: [td.arrayNode, endEnd+1]];
TiogaOps.InsertChar['\n];
TiogaOps.SetSelection[
viewer: cd.viewer,
start: [td.arrayNode, base],
end: [td.arrayNode, base + td.lineS[cd.line].tiogaCount]];
TiogaOps.Delete[];
};
};
TiogaOps.CallWithLocks[WithLocks, td.root];
SetLines[cd, td];
};
ClearTo:
PROC [cd: CharDisplay, where: Where] = {
td: TiogaDisplay ← NARROW[cd.otherInstanceData];
lastLine:
INT ←
SELECT where
FROM
EndOfLine => cd.line,
EndOfScreen => cd.det.lines-1,
ENDCASE => ERROR;
beginBase: INT ← TiogaOps.GetTextKey[node: td.arrayNode, key: td.lineS[cd.line].key].where;
endBase: INT ← TiogaOps.GetTextKey[node: td.arrayNode, key: td.lineS[lastLine].key].where;
Inner:
PROC [root: TiogaOps.Ref] = {
IF root # td.root THEN ERROR;
TiogaOps.SetSelection[
viewer: cd.viewer,
start: [td.arrayNode, beginBase + MIN[cd.col, td.lineS[cd.line].tiogaCount]],
end: [td.arrayNode, endBase + td.lineS[lastLine].tiogaCount],
pendingDelete: TRUE];
FOR l:
INT
IN [cd.line .. lastLine]
DO
TiogaOps.InsertChar['\n];
ENDLOOP;
};
TiogaOps.CallWithLocks[Inner, td.root];
SetLines[cd, td];
};
ClearAll:
PROC [cd: CharDisplay] = {
td: TiogaDisplay ← NARROW[cd.otherInstanceData];
begin: INT ← TiogaOps.GetTextKey[node: td.arrayNode, key: td.lineS[0].key].where;
end: INT ← TiogaOps.GetTextKey[node: td.arrayNode, key: td.lineS[cd.det.lines-1].key].where + td.lineS[cd.det.lines-1].tiogaCount;
WithLocks:
PROC [root: TiogaOps.Ref] = {
IF root # td.root THEN ERROR;
TiogaOps.SetSelection[
viewer: cd.viewer,
start: [td.arrayNode, begin],
end: [td.arrayNode, end],
pendingDelete: TRUE];
FOR l:
INT
IN [0 .. cd.det.lines)
DO
TiogaOps.InsertChar['\n];
ENDLOOP;
};
TiogaOps.CallWithLocks[WithLocks, td.root];
SetLines[cd, td];
};
SetEmph:
PROC [cd: CharDisplay, emph: Emph, on:
BOOL] = {
td: TiogaDisplay ← NARROW[cd.otherInstanceData];
cd.emphs[emph] ← on;
td.looks ← "f";
IF cd.emphs[underline] THEN td.looks ← td.looks.Cat["z"];
IF cd.emphs[standout1] THEN td.looks ← td.looks.Cat["b"];
IF cd.emphs[standout2] THEN td.looks ← td.looks.Cat["i"];
};
Emphasize:
PROC [cd: CharDisplay, emph: Emph, on:
BOOL] = {
td: TiogaDisplay ← NARROW[cd.otherInstanceData];
loc: INT ← TiogaOps.GetTextKey[node: td.arrayNode, key: td.lineS[cd.line].key].where + cd.col;
looks:
ROPE ←
SELECT emph
FROM
underline => "z",
standout1 => "b",
standout2 => "i",
ENDCASE => ERROR;
WithLocks:
PROC [root: TiogaOps.Ref] = {
IF root # td.root THEN ERROR;
TiogaOps.SetSelection[
viewer: cd.viewer,
start: [td.arrayNode, loc],
end: [td.arrayNode, loc]];
(IF on THEN TiogaOps.AddLooks ELSE TiogaOps.SubtractLooks)[looks];
};
IF cd.col > td.lineS[cd.line].tiogaCount THEN RETURN;
TiogaOps.CallWithLocks[WithLocks, td.root];
};
SetFont:
PROC [cd: CharDisplay, font:
ROPE] = {
td: TiogaDisplay ← NARROW[cd.otherInstanceData];
TiogaOps.PutProp[
n: td.root,
name: $Postfix,
value:
IO.PutFR[
"(look.f) \"fixed-pitch font\" {\"%g\" family} StyleRule",
IO.rope[font]
]
];
};
Flash:
PROC [cd: CharDisplay] = {
MessageWindow.Append["Beep", TRUE];
MessageWindow.Blink[];
MessageWindow.Clear[];
};
InitTiogaDisplayClass[];
}.