Known bugs and shortcomings:
Smooth interaction of selection/pasting with tioga viewers.
Emphasis is not written to the transcript.
Add UserProfile opotions:
default terminal type,
initial lines in terminal and in transcript,
font,
transcript visibility
Blink the cursor?
Use DoWithBuffer in the paint proc?
Reduce number of repaints on size changes
Add mouse support -- Notify proc translates mouse clicks to terminal coordinates then uses a User Profile specified pattern string to generate terminal type-in.
The display is represented as a sequence of REF TEXTs. The length field of the TEXT is always ignored, and the TEXT is always padded with space characters. The paint code always strips trailing blanks before painting the line. The problem with this is that emphasis on trailing spaces does not get painted. It also seems cruftier than maintaining an accurate length for each TEXT.
When the transcript in not visible, the space between the menu rule and the terminal is whatever was left over after allocating a whole number of lines. It would be better if this slop appeared at the bottom of the terminal viewer rather than at the top. That would make the full repaint out of the adjust proc unnecessary sometimes. As it is now, the position of the characters on the screen changes by up to +-slop whenever the size of the viewer gets smaller, so we must do a repaint then. If the slop were at the bottom the characters would not move when the viewer got smaller.
SimpleCharDisplayClass: CharDisplayClass =
NEW [CharDisplayClassRep ¬ [
name: "Simple",
Init: SDInit,
ChangeDetails: SDChangeDetails,
DeleteChar: SDDeleteChar,
TakeChar: SDTakeChar,
CursorMove: SDCursorMove,
Line: SDLine,
ClearTo: SDClearTo,
ClearAll: SDClearAll,
SetEmph: SDSetEmph,
Emphasize: SDEmphasize,
SetFont: SDSetFont,
Beep: SDBeep,
Flush: SDFlush,
Destroyed: SDDestroyed
]];
SDInit:
PROC [cd: CharDisplay, initData:
REF
ANY] = {
sd: SimpleDisplayState ¬ NEW [ SimpleDisplayStateRep ¬ [driver: NARROW[initData]] ];
cd.otherInstanceData ¬ sd;
InitCharArray[ sd, cd.det.lines, cd.det.columns ];
[cd.viewer, cd.fromDisplay] ¬ SimpleDisplaysViewer[sd, cd];
};
SDDeleteChar:
ENTRY PROC [cd: CharDisplay] = {
ENABLE UNWIND => NULL;
sd: SimpleDisplayState = NARROW[cd.otherInstanceData];
line: REF LineRep = sd.theLines[cd.line];
chars: REF TEXT = line.chars;
IF sd.debugChars THEN { sd.out.PutRope["<dc>"]; };
FOR i:
INT
IN [cd.col .. cd.det.columns-1)
DO
chars[i] ¬ chars[i+1];
ENDLOOP;
chars[cd.det.columns-1] ¬ Ascii.SP;
IF line.hasSomeEmphasis
THEN {
emphs: REF LineEmphsRep ¬ line.emphs;
FOR i:
INT
IN [cd.col .. cd.det.columns-1)
DO
emphs[i] ¬ emphs[i+1];
ENDLOOP;
emphs[cd.det.columns-1] ¬ ALL[FALSE];
};
NoteChange[ cd, sd, line, delete1, cd.col ];
};
SDTakeChar:
ENTRY PROC [cd: CharDisplay, char:
CHAR, insert:
BOOL ¬
FALSE] = {
ENABLE UNWIND => NULL;
sd: SimpleDisplayState = NARROW[cd.otherInstanceData];
line: REF LineRep = sd.theLines[cd.line];
chars: REF TEXT ¬ line.chars;
IF sd.debugChars
THEN {
IF insert THEN {sd.out.PutChar['←]; };
SELECT char
FROM
IN [Ascii.SP .. Ascii.DEL) => {sd.out.PutChar[char]; };
ENDCASE => {sd.out.PutF1["<%h>", IO.char[char]]; };
};
IF insert
THEN {
FOR i:
INT
DECREASING
IN [cd.col+1 .. cd.det.columns)
DO
chars[i] ¬ chars[i-1];
ENDLOOP;
IF line.hasSomeEmphasis
THEN {
emphs: REF LineEmphsRep ¬ line.emphs;
FOR i:
INT
DECREASING
IN [cd.col+1 .. cd.det.columns)
DO
emphs[i] ¬ emphs[i-1];
ENDLOOP;
};
chars[cd.col] ¬ char;
line.emphs[cd.col] ¬ sd.emphs;
line.hasSomeEmphasis ¬ sd.hasSomeEmphasis OR line.hasSomeEmphasis;
NoteChange[ cd, sd, line, (IF insert THEN insert1 ELSE over1), cd.col ];
CursorMoveAux[cd, 0, 1, TRUE, TRUE, TRUE, FALSE];
};
SDCursorMove:
ENTRY PROC [cd:CharDisplay, line,col:
INT, relative:
BOOL¬
FALSE, doLine,doCol:
BOOL ¬
TRUE] = {
ENABLE UNWIND => NULL;
CursorMoveAux[cd,line,col,relative,doLine,doCol,TRUE]
} ;
CursorMoveAux:
INTERNAL PROC [cd:CharDisplay, line,col:
INT, relative,doLine,doCol,report:
BOOL] = {
sd: SimpleDisplayState = NARROW[cd.otherInstanceData];
L:INTEGER = cd.det.lines;
C:INTEGER = cd.det.columns;
IF sd.debugCM
AND report
THEN {
IF relative THEN sd.out.PutRope["<cm+" ] ELSE sd.out.PutRope["<cm "];
IF doLine THEN sd.out.PutF1["%g,", IO.int[line]] ELSE sd.out.PutRope[".,"];
IF doCol THEN sd.out.PutF1["%g>", IO.int[col]] ELSE sd.out.PutRope[".>"];
};
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 / C;
line ¬ line + dl;
col ¬ col - dl * C;
WHILE col < 0 DO col ¬ col + C; line ¬ line - 1 ENDLOOP;
}
ELSE
col ¬ MAX[MIN[col, C-1], 0];
IF line < 0 THEN line ¬ 0;
IF cd.det.scrolls
THEN
IF line >= L
THEN
{
Scroll[cd, 0, L, MIN[ (line-L)+1, L ] ];
line ¬ L - 1
}
cd.line ¬ line;
cd.col ¬ col;
};
Scroll:
INTERNAL PROC [cd: CharDisplay, start, size, scrollReq:
INTEGER] ~ {
-- start is the index in the lines array of the topmost line to be moved by the scroll
-- size is the index 1 more than the last line in the region being scrolled
-- ABS[scrollReq] may be [0 .. size - start]
-- if scrollReq<0 then insert blank lines at start, remove lines from EOS (scroll down)
-- if scrollReq>0 then delete lines at start, insert blank lines at EOS (scroll up)
sd: SimpleDisplayState = NARROW[cd.otherInstanceData];
shift: INTEGER = IF scrollReq < 0 THEN (size-start)+scrollReq ELSE scrollReq;
-- this loop really is just scrolling up, for scrolling down shift is hacked to get
-- the equivalent of the scroll down by scrolling up.
upFillBase: INTEGER = size - shift;
IF sd.debugScroll
THEN
sd.out.PutF["Scroll[start:%g,size:%g,req:%g]\n",IO.int[start],IO.int[size],IO.int[scrollReq] ];
IF shift IN (0..size-start)
THEN {
open: INTEGER ¬ start + shift; -- this line will eventually be in position start
headLine: REF LineRep = sd.theLines[open];
WHILE open # start
DO
source:INTEGER = IF (open<upFillBase) THEN (open+shift) ELSE (start+open-upFillBase);
sd.theLines[open] ¬ sd.theLines[source];
open ¬ source;
ENDLOOP;
sd.theLines[start] ¬ headLine;
};
-- Log and clear filled lines
{
fillBase:INTEGER = IF scrollReq < 0 THEN start ELSE upFillBase;
logLine:BOOLEAN = start = 0 AND scrollReq = 1 AND sd.logging;
FOR i:
INTEGER
IN [fillBase .. fillBase+
ABS[scrollReq])
DO
line: REF LineRep = sd.theLines[i];
chars: REF TEXT = line.chars;
emphs: REF LineEmphsRep = line.emphs;
line.modified ¬ new;
line.hasSomeEmphasis ¬ FALSE;
IF logLine THEN LogTranscriptLine[sd, chars];
FOR j:
INTEGER
IN [0 .. chars.maxLength)
DO
chars[j] ¬ Ascii.SP;
emphs[j] ¬ ALL[ FALSE ];
ENDLOOP;
ENDLOOP;
};
-- Flush changes to display
IF sd.flushMode = FlushOnScroll
THEN {
ViewerForkers.ForkPaint[
viewer: sd.aViewer,
hint: ViewerClasses.PaintHint.client,
clearClient: FALSE,
whatChanged: $LINES,
tryShortCuts: FALSE
];
WAIT sd.paintDone;
};
};
SDLine:
ENTRY PROC [cd: CharDisplay, insert:
BOOL] = {
ENABLE UNWIND => NULL;
sd: SimpleDisplayState = NARROW[cd.otherInstanceData];
IF sd.debugOps
THEN {
sd: SimpleDisplayState = NARROW[cd.otherInstanceData];
msg: ROPE ¬ IF insert THEN "<insert line>" ELSE "<delete line>";
sd.out.PutRope[msg];
};
Scroll[ cd, cd.line, cd.det.lines, IF insert THEN -1 ELSE 1 ];
};
SDClearTo:
ENTRY PROC [cd: CharDisplay, where: Where] = {
ENABLE UNWIND => NULL;
sd: SimpleDisplayState = NARROW[cd.otherInstanceData];
line: REF LineRep = sd.theLines[cd.line];
chars: REF TEXT ¬ line.chars;
IF sd.debugOps
THEN {
sd: SimpleDisplayState = NARROW[cd.otherInstanceData];
msg:
ROPE ¬
SELECT where
FROM
EndOfLine => "<clr eol>",
EndOfScreen => "<clr eos>",
ENDCASE => ERROR;
sd.out.PutRope[msg];
};
FOR j: INT IN [cd.col .. cd.det.columns) DO chars[j] ¬ Ascii.SP; ENDLOOP;
IF line.hasSomeEmphasis
THEN {
emphs: REF LineEmphsRep ¬ line.emphs;
FOR j: INT IN [cd.col .. cd.det.columns) DO emphs[j] ¬ ALL[ FALSE ]; ENDLOOP;
};
IF cd.col = 0
THEN {
line.modified ¬ new;
line.hasSomeEmphasis ¬ FALSE;
}
ELSE
NoteChange[ cd, sd, line, tail, cd.col ];
IF where = EndOfScreen
AND cd.line+1 < cd.det.lines
THEN
Scroll[ cd, cd.line+1, cd.det.lines, cd.det.lines - (cd.line+1) ]
};
SDClearAll:
ENTRY PROC [cd: CharDisplay] = {
ENABLE UNWIND => NULL;
sd: SimpleDisplayState = NARROW[cd.otherInstanceData];
IF sd.debugOps THEN { sd.out.PutRope["<clear all>"] };
Scroll[ cd, 0, cd.det.lines, cd.det.lines ];
};
EmphNames:
ARRAY Emph
OF
ROPE ¬ [
underline: "underline",
bold: "bold",
italic: "italic",
inverse: "inverse"];
SDSetEmph:
ENTRY PROC [cd: CharDisplay, emph: Emph, on:
BOOL] = {
ENABLE UNWIND => NULL;
sd: SimpleDisplayState = NARROW[cd.otherInstanceData];
IF sd.debugEmph THEN sd.out.PutF["<%g %g>", IO.rope[EmphNames[emph]], IO.bool[on]];
sd.emphs[emph] ¬ on;
sd.hasSomeEmphasis ¬ sd.emphs[underline] OR sd.emphs[bold] OR sd.emphs[italic] OR sd.emphs[inverse] ;
};
SDEmphasize:
ENTRY PROC [cd: CharDisplay, emph: Emph, on:
BOOL] = {
ENABLE UNWIND => NULL;
sd: SimpleDisplayState = NARROW[cd.otherInstanceData];
IF sd.debugEmph THEN sd.out.PutF["<%g %g@>", IO.rope[EmphNames[emph]], IO.bool[on]];
sd.theLines[cd.line].emphs[cd.col][emph] ¬ on;
NoteChange[ cd, sd, sd.theLines[cd.line], over1, cd.col ];
};
SDSetFont:
ENTRY PROC [cd: CharDisplay, font:
ROPE] = {
ENABLE UNWIND => NULL;
sd: SimpleDisplayState = NARROW[cd.otherInstanceData];
IF sd.debugOps THEN sd.out.PutF1["<font %g>", IO.rope[font]];
};
SDBeep:
ENTRY PROC [cd: CharDisplay] = {
ENABLE UNWIND => NULL;
IF Beeps.can THEN Beeps.Beep[ frequency:1000, duration:65 ] -- Hz, milliseconds
ELSE {
MessageWindow.Append["Beep", TRUE];
MessageWindow.Blink[];
MessageWindow.Clear[];
};
};
SDFlush:
PROC [cd: CharDisplay] ~ {
sd: SimpleDisplayState = NARROW[cd.otherInstanceData];
ViewerOps.PaintViewer[sd.aViewer, ViewerOps.PaintHint.client, FALSE, $LINES];
sd.changeCount ¬ 0;
};
SDDestroyed:
ENTRY PROC [cd: CharDisplay]
RETURNS [b:
BOOL] = {
ENABLE UNWIND => NULL;
sd: SimpleDisplayState = NARROW[cd.otherInstanceData];
b ¬ sd.aViewer.destroyed;
};
Helpers
InitSimpleDisplayClass:
PROC ~ {
RegClass[SimpleCharDisplayClass];
};
InitCharArray:
PROC [sd: SimpleDisplayState, r,c:
INT] ~ {
sd.theLines ¬ NEW[LinesRep[r]];
FOR i:
INT
IN [0..r)
DO
lineEmphs: REF LineEmphsRep ¬ NEW[LineEmphsRep[c]];
lineChars: REF TEXT ¬ NEW[TEXT[c]];
FOR j:
INT
IN [0..c)
DO
lineEmphs[j] ← ALL[FALSE]; -- defaults to false
lineChars[j] ¬ Ascii.SP;
ENDLOOP;
sd.theLines[i] ¬
NEW [LineRep ¬ [ emphs: lineEmphs, chars: lineChars, screenLine: i ]];
ENDLOOP;
};
ResizeTerminal:
PUBLIC PROC [ cd: CharDisplay, sd: SimpleDisplayState, r,c:
INT ] ~ {
oldLines: REF LinesRep ¬ sd.theLines;
oldr: INT ¬ cd.det.lines;
oldc: INT ¬ cd.det.columns;
oldCopyBase : INT ¬ IF oldr > r THEN oldr - r ELSE 0;
InitCharArray[ sd, r, c ];
FOR i:
INT
IN [0..
MIN[r,oldr])
DO
oldes: REF LineEmphsRep ¬ oldLines[oldCopyBase + i].emphs;
newes: REF LineEmphsRep ¬ sd.theLines[i].emphs;
oldcs: REF TEXT ¬ oldLines[oldCopyBase + i].chars;
newcs: REF TEXT ¬ sd.theLines[i].chars;
sd.theLines[i].hasSomeEmphasis ¬ oldLines[oldCopyBase + i].hasSomeEmphasis;
FOR j:
INT
IN [0..
MIN[c,oldc])
DO
newes[j] ¬ oldes[j];
newcs[j] ¬ oldcs[j];
ENDLOOP;
ENDLOOP;
cd.det.lines ¬ r;
cd.det.columns ¬ c;
cd.line ¬ MIN[ cd.line, r-1 ];
cd.col ¬ MIN[ cd.col, c-1 ];
};
DumpCharArray: PROC [cd: CharDisplay] ~ {
sd: SimpleDisplayState = NARROW[cd.otherInstanceData];
r: INTEGER = cd.det.lines;
c: INTEGER = cd.det.columns;
sd.out.PutF["--%g--\n", IO.int[sd.theLines.count]];
FOR i:INTEGER IN [0..r) DO
es: REF LineEmphsRep ← sd.theLines[i].emphs;
cs: REF TEXT ← sd.theLines[i].chars;
sd.out.PutF["%g(%g): ", IO.int[i], IO.int[cs.maxLength]];
FOR j: INTEGER IN [0..c) DO
ch:CHAR ← cs[j];
sd.out.PutChar[ch];
ENDLOOP;
sd.out.PutF["\n"];
ENDLOOP;
};
NoteChange:
INTERNAL PROC [cd: CharDisplay, sd: SimpleDisplayState, line:
REF LineRep, mod:ModificationType, col:
INT] ~ {
IF line.modified = unchanged
THEN {
line.modified ¬ mod;
line.tailStartColumn ¬ col
}
ELSE
IF line.modified = new
THEN
NULL
ELSE {
line.modified ¬ tail;
line.tailStartColumn ¬ MIN[ line.tailStartColumn, col ];
};
IF sd.flushMode = FlushOnChangeCount
THEN
IF sd.changeCount >= sd.changeCountLimit
THEN {
sd.changeCount ¬ 0;
ViewerForkers.ForkPaint[
viewer: sd.aViewer,
hint: ViewerClasses.PaintHint.client,
clearClient: FALSE,
whatChanged: $LINES,
tryShortCuts: FALSE
];
WAIT sd.paintDone;
ViewerOps.PaintViewer[sd.aViewer, ViewerOps.PaintHint.client, FALSE, $LINES]
}
ELSE {
sd.changeCount ¬ sd.changeCount + 1;
}
};
InitSimpleDisplayClass[];