<> <> <> <> <<>> DIRECTORY Ascii USING [CR], Atom USING [DottedPairNode, PropList], EditSpan USING [CannotDoEdit, Transpose], EditSpanSupport USING [CopySpan], FS USING [ErrorGroup, OpenFile], IO USING [PutChar, RopeFromROS, ROS, STREAM], LooksReader USING [Body, BumpIndex, FreeLooksReader, Get, GetIndex, GetLooksReader, Peek, Ref, SetIndex, SetPosition], MessageWindow USING [Append, Blink], NameSymbolTable USING [MakeNameFromRope, RopeFromName], NodeProps USING [DoSpecs, GetSpecs, MapProps, PutProp], Process USING [Detach], PutGet USING [FromFile, FromFileC, ToFile, ToFileC], Rope USING [Concat, FromRefText, Length, ROPE, Substr], RopeReader USING [Body, BumpIndex, FreeRopeReader, Get, GetIndex, GetRope, GetRopeReader, Peek, Ref, SetIndex, SetPosition], RunReader USING [Get, GetIndex, GetRuns, Peek, SetIndex, SetPosition], SafeStorage USING [CantEstablishFinalization, EnableFinalization, EstablishFinalization, FinalizationQueue, FQNext, NewFQ], TEditDocument USING [Selection, TEditDocumentData], TEditInput USING [currentEvent, FreeTree], TEditInputOps USING [CallWithLocks, Delete], TEditSelection USING [GetSelectionGrain, MakeSelection], TextLooks USING [Concat, CreateRun, Looks, Runs, Size, Substr], TextLooksSupport USING [], TextNode USING [Body, FirstChild, Forward, LastLocWithin, LastWithin, Level, Location, LocOffset, LocWithin, MakeNodeLoc, MakeNodeSpan, NewTextNode, NodeItself, Offset, Parent, Ref, Root, Span, TextBody], TiogaAccess USING [Buffer, BufferRep, CharWithLooks, Looks, NodeInfo], ViewerClasses USING [Viewer], ViewerOps USING [PaintViewer]; TiogaAccessImpl: CEDAR PROGRAM IMPORTS EditSpan, EditSpanSupport, IO, LooksReader, MessageWindow, NameSymbolTable, NodeProps, Process, PutGet, Rope, RopeReader, SafeStorage, TEditInput, TEditInputOps, TEditSelection, TextLooks, TextNode, ViewerOps EXPORTS TiogaAccess ~ BEGIN OPEN TiogaAccess; ROPE: TYPE ~ Rope.ROPE; ropePieces: NAT ~ 15; textBufSize: NAT _ (574-SIZE[TEXT[0]])*2; <> PutBackBufferSize: NAT _ 100; maxPieceSize: INT ~ (INT[64]*1024-8-SIZE[BufferRep[0]])/SIZE[CharWithLooks]; Reader: TYPE ~ REF ReaderRep; ReaderRep: PUBLIC TYPE ~ RECORD [ root: TextNode.Ref, textNode: TextNode.Ref, textNodeIndex: INT, nodeInfo: NodeInfo, nodeLength: INT, ropeReader: RopeReader.Ref, looksReader: LooksReader.Ref, end: BOOLEAN, startingOffsetFromOriginalRoot: TextNode.Offset, putback: LIST OF Buffer <> ]; Error: PUBLIC ERROR [group: FS.ErrorGroup, expl: ROPE] ~ CODE; ObtainBuffer: PUBLIC PROC [pieceSize: NAT] RETURNS [Buffer] ~ { buffer: Buffer _ NEW[BufferRep[pieceSize]]; buffer.length _ 0; buffer.next _ NIL; RETURN [buffer] }; NextPieceSize: PROC [pieceSize: NAT] RETURNS [NAT] ~ { RETURN [MIN[maxPieceSize, 2*pieceSize]]; }; ReleaseBuffer: PUBLIC PROC [buffer: Buffer] ~ { IF buffer # NIL THEN { ReleaseBuffer[buffer.next]; buffer.next _ NIL; }; }; BufferSize: PUBLIC PROC [buffer: Buffer] RETURNS [INT] ~ { size: INT _ 0; WHILE buffer # NIL DO size _ size + buffer.length; buffer _ buffer.next; ENDLOOP; RETURN [size]; }; SetBufferSize: PUBLIC PROC [buffer: Buffer, newSize: INT] ~ { WHILE buffer.maxLength < newSize DO buffer.length _ buffer.maxLength; IF buffer.next = NIL THEN buffer.next _ ObtainBuffer[NextPieceSize[buffer.maxLength]]; newSize _ newSize - buffer.length; buffer _ buffer.next; ENDLOOP; buffer.length _ newSize; ReleaseBuffer[buffer.next]; buffer.next _ NIL; }; Append: PUBLIC PROC [buffer: Buffer, value: CharWithLooks] ~ { WHILE buffer.next # NIL DO buffer _ buffer.next; ENDLOOP; IF buffer.length = buffer.maxLength THEN { buffer.next _ ObtainBuffer[NextPieceSize[buffer.maxLength]]; buffer _ buffer.next; buffer.length _ 0; }; InlineAppend[buffer, value]; }; Store: PUBLIC PROC [buffer: Buffer, index: INT, value: CharWithLooks] ~ { UNTIL buffer = NIL OR index < buffer.length DO index _ index-buffer.length; buffer _ buffer.next; ENDLOOP; IF buffer = NIL THEN Error[client, "Index exceeds buffer bounds"]; buffer[index] _ value; }; Fetch: PUBLIC PROC [buffer: Buffer, index: INT] RETURNS [CharWithLooks] ~ { UNTIL buffer = NIL OR index < buffer.length DO index _ index-buffer.length; buffer _ buffer.next; ENDLOOP; IF buffer = NIL THEN Error[client, "Index exceeds buffer bounds"]; RETURN [buffer[index]]; }; GetExternalProp: PUBLIC PROC [key: REF, value: REF] RETURNS [ROPE] ~ { RETURN [NodeProps.GetSpecs[NARROW[key], value]] }; GetInternalProp: PUBLIC PROC [key: REF, value: ROPE] RETURNS [REF] ~ { RETURN [NodeProps.DoSpecs[NARROW[key], value]] }; MakePropList: PROC [textNode: TextNode.Ref] RETURNS [props: Atom.PropList _ NIL] ~ { Action: PROC [name: ATOM, value: REF] RETURNS [BOOLEAN _ FALSE] ~ { props _ CONS[NEW[Atom.DottedPairNode _ [key: name, val: NodeProps.GetSpecs[name, value]]], props]; }; [] _ NodeProps.MapProps[n: textNode, action: Action, typeFlag: FALSE, commentFlag: FALSE]; }; StartNewNode: PROC [reader: Reader, offset: INT _ 0] ~ { IF reader.textNode = NIL THEN { reader.nodeInfo.format _ NIL; reader.nodeInfo.comment _ FALSE; reader.nodeInfo.props _ NIL; } ELSE { reader.nodeInfo.format _ NameSymbolTable.RopeFromName[reader.textNode.typename]; reader.nodeInfo.props _ MakePropList[reader.textNode]; }; WITH reader.textNode SELECT FROM t: REF TextNode.Body.text => { reader.nodeLength _ Rope.Length[t.rope]; reader.nodeInfo.comment _ t.comment; RopeReader.SetPosition[reader.ropeReader, t.rope, offset]; LooksReader.SetPosition[reader.looksReader, t.runs, offset]; }; ENDCASE => { reader.nodeLength _ 0; reader.nodeInfo.comment _ TRUE; RopeReader.SetPosition[reader.ropeReader, NIL]; LooksReader.SetPosition[reader.looksReader, NIL]; }; }; InitIndex: PROC [reader: Reader] ~ { reader.textNodeIndex _ TextNode.LocOffset[loc1: [reader.root, 0], loc2: [reader.textNode, 0], skipCommentNodes: FALSE]; }; FromNode: PROC [textNode: TextNode.Ref, offset: TextNode.Offset _ INT.FIRST] RETURNS [Reader] ~ { new: Reader ~ NEW[ReaderRep]; new.root _ TextNode.Root[textNode]; new.textNode _ textNode; new.nodeInfo.level _ TextNode.Level[textNode]; new.ropeReader _ RopeReader.GetRopeReader[]; new.looksReader _ LooksReader.GetLooksReader[]; new.end _ (textNode = NIL); StartNewNode[new]; InitIndex[new]; new.startingOffsetFromOriginalRoot _ 0; IF offset # INT.FIRST THEN new.startingOffsetFromOriginalRoot _ offset-GetPosition[new]; SafeStorage.EnableFinalization[new]; RETURN [new]; }; FromNothing: PUBLIC PROC RETURNS [Reader] ~ { RETURN [FromNode[NIL]] }; FromFile: PUBLIC PROC [fileName: ROPE] RETURNS [Reader] ~ { RETURN [FromNode[PutGet.FromFile[fileName]]] }; FromOpenFile: PUBLIC PROC [openFile: FS.OpenFile] RETURNS [Reader] ~ { RETURN [FromNode[PutGet.FromFileC[openFile]]] }; NodeSpanFromSelection: PROC[sel: TEditDocument.Selection] RETURNS[span: TextNode.Span] = { RETURN[TextNode.MakeNodeSpan[first: sel.start.pos.node, last: sel.end.pos.node]] }; SelectionSpan: PROC[sel: TEditDocument.Selection] RETURNS[span: TextNode.Span] = { IF TEditSelection.GetSelectionGrain[sel]=node THEN RETURN[NodeSpanFromSelection[sel]] ELSE RETURN[[start: sel.start.pos, end: sel.end.pos]]; }; CreateChain: PROC [levels: INT] RETURNS [root, last: TextNode.Ref] ~ { <> IF levels <= 0 THEN RETURN [NIL, NIL]; root _ TextNode.NewTextNode[]; root.last _ TRUE; last _ root; THROUGH [0..levels) DO n: REF TextNode.Body.text _ TextNode.NewTextNode[]; n.last _ TRUE; last.child _ n; last _ n; ENDLOOP; }; FromSelection: PUBLIC PROC RETURNS [Reader] ~ { textNode: TextNode.Ref _ NIL; myOffset: TextNode.Offset _ 0; CopySelection: PROC [root: TextNode.Ref, tSel: TEditDocument.Selection] ~ { IF tSel.granularity = point THEN NULL ELSE { span: TextNode.Span _ SelectionSpan[tSel]; copy: TextNode.Span _ EditSpanSupport.CopySpan[span]; myOffset _ TextNode.LocOffset[loc1: TextNode.MakeNodeLoc[root], loc2: span.start, skipCommentNodes: TRUE]; IF copy.end.where # TextNode.NodeItself THEN { WITH copy.end.node SELECT FROM t: REF TextNode.Body.text => { t.rope _ Rope.Substr[t.rope, 0, copy.end.where+1]; t.runs _ TextLooks.Substr[t.runs, 0, copy.end.where+1]; }; ENDCASE => NULL; }; IF copy.start.where > 0 THEN { WITH copy.start.node SELECT FROM t: REF TextNode.Body.text => { t.rope _ Rope.Substr[t.rope, copy.start.where, INT.LAST]; t.runs _ TextLooks.Substr[t.runs, copy.start.where, INT.LAST]; copy.start.where _ 0; }; ENDCASE => NULL; }; textNode _ copy.start.node; }; }; TEditInputOps.CallWithLocks[CopySelection, read]; RETURN [FromNode[textNode, myOffset]] }; FromViewer: PUBLIC PROC [viewer: ViewerClasses.Viewer] RETURNS [Reader] ~ { textNode: TextNode.Ref _ NIL; WITH viewer.data SELECT FROM tdd: TEditDocument.TEditDocumentData => { r: TextNode.Ref _ tdd.text; r _ TextNode.Root[ EditSpanSupport.CopySpan[ -- adds an extra r node TextNode.MakeNodeSpan[r, TextNode.LastWithin[r]] ] .start.node ]; textNode _ TextNode.FirstChild[r]; -- get rid of the extra r node textNode.next _ NIL; -- no parent or siblings r.child _ NIL; r _ NIL;-- don't want r any more }; ENDCASE => NULL; RETURN [FromNode[textNode]] }; EndOf: PUBLIC PROC [reader: Reader] RETURNS [BOOLEAN] ~ { RETURN [reader.putback = NIL AND reader.end] }; Get: PUBLIC PROC [reader: Reader] RETURNS [CharWithLooks] ~ { IF reader.putback # NIL THEN { buf: Buffer _ reader.putback.first; candl: CharWithLooks _ buf[buf.length-1]; IF (buf.length _ buf.length - 1) = 0 THEN { t: LIST OF Buffer _ reader.putback.rest; reader.putback.rest _ NIL; reader.putback _ t; ReleaseBuffer[buf]; }; RETURN [candl] } ELSE IF reader.end THEN {ERROR Error[client, "Attempt to read past end of document"]} ELSE IF RopeReader.GetIndex[reader.ropeReader] = reader.nodeLength THEN { SkipToNextNode[reader]; RETURN [[char: Ascii.CR, endOfNode: TRUE, looks: ALL[FALSE]]] } ELSE { RETURN [[char: RopeReader.Get[reader.ropeReader], endOfNode: FALSE, looks: LooksReader.Get[reader.looksReader]]] }; }; GetIndex: PUBLIC PROC [reader: Reader] RETURNS [index: INT] ~ { index _ reader.textNodeIndex + reader.ropeReader.GetIndex; <> }; GetLength: PUBLIC PROC [reader: Reader] RETURNS [INT] ~ { RETURN [TextNode.LocOffset[ loc1: [reader.root, 0], loc2: TextNode.LastLocWithin[reader.root], skipCommentNodes: FALSE ]+1]; }; GetPosition: PUBLIC PROC [reader: Reader] RETURNS [position: INT] ~ { position _ TextNode.LocOffset[loc1: [reader.root, 0], loc2: [reader.textNode, reader.ropeReader.GetIndex], skipCommentNodes: TRUE] + reader.startingOffsetFromOriginalRoot; }; SetIndex: PUBLIC PROC [reader: Reader, index: INT] ~ { DiscardPutBack[reader]; IF index IN [reader.textNodeIndex..reader.textNodeIndex+NodeSize[reader.textNode]) THEN { <> offset: INT _ index - reader.textNodeIndex; RopeReader.SetIndex[reader.ropeReader, offset]; LooksReader.SetIndex[reader.looksReader, offset]; } ELSE SetLoc[reader, TextNode.LocWithin[n: reader.root, count: index, skipCommentNodes: FALSE]] }; SetPosition: PUBLIC PROC [reader: Reader, position: INT] ~ { DiscardPutBack[reader]; SetLoc[reader, TextNode.LocWithin[n: reader.root, count: position - reader.startingOffsetFromOriginalRoot, skipCommentNodes: TRUE]]; }; SetLoc: PROC [reader: Reader, location: TextNode.Location] ~ { reader.textNode _ location.node; StartNewNode[reader, IF location.where = TextNode.NodeItself THEN 0 ELSE location.where]; InitIndex[reader]; }; GetNodeInfo: PUBLIC PROC [reader: Reader] RETURNS [NodeInfo] ~ { RETURN [reader.nodeInfo] }; GetBufferBlock: PROC [reader: Reader, buffer: Buffer, maxLength: INT] RETURNS [endOfNode: BOOLEAN] ~ { n: NAT ~ MIN[maxLength, buffer.maxLength-buffer.length]; FOR i: NAT IN [buffer.length..buffer.length+n) DO IF (buffer[i] _ Get[reader]).endOfNode THEN { buffer.length _ i+1; RETURN [TRUE] }; ENDLOOP; buffer.length _ buffer.length+n; RETURN [FALSE]; }; GetBuffer: PUBLIC PROC [reader: Reader, buffer: Buffer, start: INT, maxLength: INT] RETURNS [endOfNode: BOOLEAN _ FALSE] ~ { WHILE buffer.maxLength <= start DO buffer.length _ buffer.maxLength; IF buffer.next = NIL THEN buffer.next _ ObtainBuffer[NextPieceSize[buffer.maxLength]]; start _ start - buffer.length; buffer _ buffer.next; ENDLOOP; buffer.length _ start; UNTIL maxLength = 0 DO endOfNode _ GetBufferBlock[reader, buffer, maxLength].endOfNode; IF endOfNode THEN EXIT; maxLength _ maxLength - buffer.length; IF buffer.next = NIL THEN buffer.next _ ObtainBuffer[NextPieceSize[buffer.maxLength]]; buffer _ buffer.next; buffer.length _ 0; ENDLOOP; ReleaseBuffer[buffer.next]; buffer.next _ NIL; }; Peek: PUBLIC PROC [reader: Reader] RETURNS [CharWithLooks] ~ { IF reader.putback # NIL THEN { buf: Buffer _ reader.putback.first; RETURN [buf[buf.length-1]]; } ELSE IF RopeReader.GetIndex[reader.ropeReader] = reader.nodeLength THEN { RETURN [[char: Ascii.CR, endOfNode: TRUE, looks: ALL[FALSE]]] } ELSE { RETURN [[char: RopeReader.Peek[reader.ropeReader], endOfNode: FALSE, looks: LooksReader.Peek[reader.looksReader]]] }; }; PeekRope: PUBLIC PROC [reader: Reader] RETURNS [rope: ROPE _ NIL] ~ { IF reader.putback # NIL THEN { <> s: IO.STREAM _ IO.ROS[]; FOR bl: LIST OF Buffer _ reader.putback, bl.rest UNTIL bl = NIL DO buf: Buffer _ bl.first; FOR i: NAT DECREASING IN [0..buf.length) DO candl: CharWithLooks _ buf[i]; IF candl.endOfNode THEN RETURN [IO.RopeFromROS[s]]; IO.PutChar[s, candl.char]; ENDLOOP; ENDLOOP; rope _ IO.RopeFromROS[s]; }; rope _ Rope.Concat[rope, Rope.Substr[reader.ropeReader.GetRope, reader.ropeReader.GetIndex]]; }; PeekRuns: PROC [reader: Reader] RETURNS [runs: TextLooks.Runs _ NIL] ~ { WITH reader.textNode SELECT FROM t: REF TextNode.Body.text => { start: INT _ reader.ropeReader.GetIndex; end: INT _ Rope.Length[t.rope]; IF t.runs # NIL THEN runs _ TextLooks.Substr[t.runs, start, end-start]; }; ENDCASE => NULL; }; SkipToNextNode: PUBLIC PROC [reader: Reader] ~ { deltaLevel: INT; IF reader.end THEN Error[client, "Attempt to read past end of document"]; reader.textNodeIndex _ reader.textNodeIndex + NodeSize[reader.textNode]; [reader.textNode, deltaLevel] _ TextNode.Forward[reader.textNode]; reader.nodeInfo.level _ reader.nodeInfo.level + deltaLevel; reader.nodeInfo.props _ NIL; reader.end _ (reader.nodeInfo.level = 0); IF reader.end THEN NULL ELSE StartNewNode[reader]; }; Full: PROC [buffer: Buffer] RETURNS [BOOLEAN] ~ INLINE {RETURN [buffer.length = buffer.maxLength]}; InlineAppend: PROC [buffer: Buffer, charWithLooks: CharWithLooks] ~ INLINE {buffer[(buffer.length _ buffer.length+1)-1] _ charWithLooks}; PutBack: PUBLIC PROC [reader: Reader, charWithLooks: CharWithLooks] ~ { IF reader.putback = NIL OR Full[reader.putback.first] THEN { buf: Buffer _ ObtainBuffer[100]; buf.length _ 0; reader.putback _ CONS[buf, reader.putback]; }; InlineAppend[reader.putback.first, charWithLooks]; }; PutBackBuffer: PUBLIC PROC [reader: Reader, buffer: Buffer, start: INT, maxLength: INT] ~ { end: NAT _ start + MIN[buffer.length-start, maxLength]; FOR i: INT IN [start..end) DO PutBack[reader, Fetch[buffer, i]]; ENDLOOP; }; DiscardPutBack: PROC [reader: Reader] ~ { UNTIL reader.putback = NIL DO t: LIST OF Buffer _ reader.putback.rest; reader.putback.rest _ NIL; reader.putback _ t; ENDLOOP; }; DoneWith: PUBLIC PROC [reader: Reader] ~ { IF reader.root # NIL THEN { TEditInput.FreeTree[reader.root]; reader.root _ reader.textNode _ NIL; RopeReader.FreeRopeReader[reader.ropeReader]; reader.ropeReader _ NIL; LooksReader.FreeLooksReader[reader.looksReader]; reader.looksReader _ NIL; reader.nodeInfo _ [format: NIL, comment: FALSE, level: 0, props: NIL]; DiscardPutBack[reader]; }; }; Writer: TYPE ~ REF WriterRep; WriterRep: PUBLIC TYPE ~ RECORD [ root: REF TextNode.TextBody, first: TextNode.Ref, last: TextNode.Ref, lastLevel: INT, nodeInfo: NodeInfo, ropes: ARRAY [0..ropePieces) OF ROPE, <> textBuf: REF TEXT, runs: TextLooks.Runs, runsSize: INT, newRunLooks: TextLooks.Looks, newRunSize: INT ]; Create: PUBLIC PROC RETURNS [Writer] ~ { new: Writer _ NEW[WriterRep]; new.root _ NIL; new.first _ new.last _ NIL; new.lastLevel _ 0; new.nodeInfo _ [format: NIL, comment: FALSE, level: 0, props: NIL]; new.textBuf _ NEW[TEXT[textBufSize]]; new.textBuf.length _ 0; new.ropes _ ALL[NIL]; new.runs _ NIL; new.runsSize _ 0; new.newRunLooks _ ALL[FALSE]; new.newRunSize _ 0; SafeStorage.EnableFinalization[new]; RETURN [new]; }; InsertRoot: PROC [writer: Writer, x: REF TextNode.Body.text] ~ { IF Rope.Length[x.rope] # 0 THEN Error[client, "Root may not contain text"]; x.last _ TRUE; writer.root _ x; writer.last _ x; }; CreateChainFromRoot: PROC [writer: Writer] ~ { <> writer.root _ TextNode.NewTextNode[]; writer.root.last _ TRUE; writer.last _ writer.root; writer.lastLevel _ 0; UNTIL writer.nodeInfo.level = writer.lastLevel + 1 DO InsertChild[writer, TextNode.NewTextNode[]]; ENDLOOP; }; RunSize: PROC [runs: TextLooks.Runs] RETURNS [INT] ~ { <> RETURN [TextLooks.Size[runs]] }; InsertSibling: PROC [writer: Writer, new: TextNode.Ref] ~ { x: TextNode.Ref ~ writer.last; new.next _ x.next; new.last _ x.last; x.next _ new; x.last _ FALSE; writer.last _ new; }; InsertChild: PROC [writer: Writer, new: TextNode.Ref] ~ { x: TextNode.Ref ~ writer.last; IF x.child # NIL THEN ERROR; new.next _ x; new.last _ TRUE; x.child _ new; writer.last _ new; writer.lastLevel _ writer.lastLevel + 1; }; FoldText: PROC [writer: Writer] ~ { FoldRope[writer, Rope.FromRefText[writer.textBuf]]; writer.textBuf.length _ 0; }; FoldRope: PROC [writer: Writer, rope: ROPE] ~ { i: NAT _ 0; writer.textBuf.length _ 0; DO IF writer.ropes[i] = NIL THEN {writer.ropes[i] _ rope; EXIT}; rope _ Rope.Concat[writer.ropes[i], rope]; writer.ropes[i] _ NIL; IF i < ropePieces-1 THEN i _ i + 1; ENDLOOP; }; GetRope: PROC [writer: Writer] RETURNS [rope: ROPE _ NIL] ~ { FoldText[writer]; FOR i: NAT IN [0..ropePieces) DO IF writer.ropes[i] # NIL THEN { rope _ Rope.Concat[writer.ropes[i], rope]; writer.ropes[i] _ NIL; }; ENDLOOP; }; FoldRuns: PROC [writer: Writer] ~ { IF writer.newRunSize > 0 THEN writer.runs _ TextLooks.Concat[base: writer.runs, rest: TextLooks.CreateRun[writer.newRunSize, writer.newRunLooks], baseLen: writer.runsSize, restLen: writer.newRunSize]; writer.runsSize _ writer.runsSize + writer.newRunSize; writer.newRunSize _ 0; }; GetRuns: PROC [writer: Writer] RETURNS [runs: TextLooks.Runs] ~ { FoldRuns[writer]; runs _ writer.runs; writer.runs _ NIL; writer.runsSize _ 0; }; Put: PUBLIC PROC [writer: Writer, charWithLooks: CharWithLooks] ~ { IF writer.nodeInfo.level < 0 THEN Error[client, "Level may not be negative"]; IF charWithLooks.endOfNode THEN { new: REF TextNode.Body.text _ TextNode.NewTextNode[]; new.typename _ NameSymbolTable.MakeNameFromRope[writer.nodeInfo.format]; new.comment _ writer.nodeInfo.comment; new.rope _ GetRope[writer]; new.runs _ GetRuns[writer]; FOR l: Atom.PropList _ writer.nodeInfo.props, l.rest UNTIL l = NIL DO key: ATOM ~ NARROW[l.first.key]; rope: ROPE ~ NARROW[l.first.val]; NodeProps.PutProp[new, key, GetInternalProp[key, rope]]; ENDLOOP; IF writer.root = NIL AND writer.nodeInfo.level = 0 THEN InsertRoot[writer, new] ELSE { IF writer.nodeInfo.level < 1 THEN Error[client, "Invalid nesting level"]; IF writer.root = NIL THEN CreateChainFromRoot[writer]; WHILE writer.nodeInfo.level < writer.lastLevel DO writer.last _ TextNode.Parent[writer.last]; writer.lastLevel _ writer.lastLevel - 1; ENDLOOP; IF writer.nodeInfo.level = writer.lastLevel THEN InsertSibling[writer, new] ELSE IF writer.nodeInfo.level = writer.lastLevel + 1 THEN InsertChild[writer, new] ELSE Error[client, "Nesting level may not increase by more than one"]; }; IF writer.first = NIL THEN writer.first _ new; writer.runsSize _ 0; } ELSE { text: REF TEXT ~ writer.textBuf; IF charWithLooks.looks # writer.newRunLooks THEN { FoldRuns[writer]; writer.newRunLooks _ charWithLooks.looks; }; writer.newRunSize _ writer.newRunSize + 1; IF text.length = text.maxLength THEN FoldText[writer]; text[(text.length _ text.length+1)-1] _ charWithLooks.char; }; }; GetNodeRefs: PUBLIC PROC [reader: Reader] RETURNS [root, current: REF, offset: INT] ~ { root _ reader.root; current _ reader.textNode; offset _ reader.ropeReader.GetIndex; }; CopyNode: PUBLIC PROC [writer: Writer, reader: Reader, maxLength: INT, copyInfo: BOOLEAN] RETURNS [endOfNode: BOOLEAN] ~ { textRope: ROPE _ NIL; textRuns: TextLooks.Runs _ NIL; textSize: INT _ 0; IF copyInfo THEN PutNodeInfo[writer, GetNodeInfo[reader]]; WHILE maxLength > 0 AND reader.putback # NIL DO candl: CharWithLooks _ Get[reader]; Put[writer, candl]; maxLength _ maxLength - 1; IF candl.endOfNode THEN RETURN [TRUE]; ENDLOOP; FoldText[writer]; FoldRuns[writer]; textRope _ PeekRope[reader]; textSize _ Rope.Length[textRope]; textRuns _ PeekRuns[reader]; endOfNode _ textSize < maxLength; IF NOT endOfNode THEN { textRope _ Rope.Substr[textRope, 0, maxLength]; textSize _ Rope.Length[textRope]; IF textRuns # NIL THEN textRuns _ TextLooks.Substr[textRuns, 0, textSize]; }; FoldRope[writer, textRope]; writer.runs _ TextLooks.Concat[base: writer.runs, rest: textRuns, baseLen: writer.runsSize, restLen: textSize]; writer.runsSize _ writer.runsSize + textSize; RopeReader.BumpIndex[reader.ropeReader, textSize]; LooksReader.BumpIndex[reader.looksReader, textSize]; IF endOfNode THEN Put[writer, Get[reader]]; }; PutNodeInfo: PUBLIC PROC [writer: Writer, nodeInfo: NodeInfo] ~ { writer.nodeInfo _ nodeInfo; }; PutBuffer: PUBLIC PROC [writer: Writer, buffer: Buffer, start: INT, maxLength: INT] ~ { WHILE buffer#NIL AND start >= buffer.length DO start _ start - buffer.length; buffer _ buffer.next; ENDLOOP; WHILE buffer#NIL AND maxLength > 0 DO n: INT _ MIN[maxLength, buffer.length-start]; FOR i: NAT IN [NAT[start]..NAT[start+n]) DO Put[writer, buffer[i]]; ENDLOOP; maxLength _ maxLength - n; start _ 0; buffer _ buffer.next; ENDLOOP; }; SetSelectionFromSpan: PROC [tSel: TEditDocument.Selection, span: TextNode.Span] ~ { IF span.start.node = span.end.node THEN { IF (span.start.where = TextNode.NodeItself) # (span.end.where = TextNode.NodeItself) THEN ERROR; -- someone made a bogus span. IF span.start.where = TextNode.NodeItself THEN { <> span.start.where _ 0; span.end.where _ MAX[NodeSize[span.end.node]-1, 0]; tSel.granularity _ node; } ELSE { <> tSel.granularity _ MIN[tSel.granularity, word]; IF span.start.where > span.end.where THEN { <> tSel.granularity _ point; span.end.where _ span.start.where; }; }; } ELSE { <> IF span.start.where = TextNode.NodeItself AND span.end.where = TextNode.NodeItself THEN { <> tSel.granularity _ MAX[tSel.granularity, node]; } ELSE { <> tSel.granularity _ char; }; IF span.start.where = TextNode.NodeItself THEN span.start.where _ 0; IF span.end.where = TextNode.NodeItself THEN span.end.where _ MAX[NodeSize[span.end.node]-1, 0]; }; <> tSel.start.pos _ span.start; tSel.end.pos _ span.end; }; NodeSize: PROC [textNode: TextNode.Ref] RETURNS [INT] ~ { WITH textNode SELECT FROM t: REF TextNode.Body.text => RETURN [Rope.Length[t.rope]]; ENDCASE => RETURN [0]; }; FinishOff: PROC [writer: Writer] ~ { <> IF writer.ropes#ALL[NIL] OR writer.textBuf.length>0 THEN Put[writer, [Ascii.CR, TRUE, ALL[FALSE]]]; }; WriteFile: PUBLIC PROC [writer: Writer, fileName: ROPE] ~ { FinishOff[writer]; [] _ PutGet.ToFile[fileName, writer.root]; Reset[writer]; }; WriteOpenFile: PUBLIC PROC [writer: Writer, openFile: FS.OpenFile] ~ { FinishOff[writer]; [] _ PutGet.ToFileC[openFile, writer.root]; Reset[writer]; }; WriteSelection: PUBLIC PROC [writer: Writer] ~ { FinishOff[writer]; IF writer.root = NIL THEN TEditInputOps.Delete[] ELSE { success: BOOL _ FALSE; lastSize: INT _ NodeSize[writer.last]; startOffset: INT _ IF lastSize=0 AND writer.first=writer.last THEN TextNode.NodeItself ELSE 0; endOffset: INT _ IF lastSize=0 THEN TextNode.NodeItself ELSE lastSize-1; alpha: TextNode.Span _ [start: [node: writer.first, where: startOffset], end: [node: writer.last, where: endOffset]]; DoTranspose: PROC [root: TextNode.Ref, tSel: TEditDocument.Selection] ~ { beta: TextNode.Span _ SelectionSpan[tSel]; documentRoot: TextNode.Ref _ TextNode.Root[beta.start.node]; IF tSel.granularity = node OR tSel.granularity = branch THEN { alpha.start.where _ alpha.end.where _ TextNode.NodeItself; }; success _ TRUE; [alpha, beta] _ EditSpan.Transpose[alphaRoot: writer.root, betaRoot: documentRoot, alpha: alpha, beta: beta, words: FALSE, event: TEditInput.currentEvent ! EditSpan.CannotDoEdit => {success _ FALSE; CONTINUE}]; IF success THEN { tSel.pendingDelete _ FALSE; SetSelectionFromSpan[tSel, alpha]; TEditSelection.MakeSelection[new: tSel, selection: primary]; }; }; TEditInputOps.CallWithLocks[DoTranspose, write]; IF NOT success THEN { MessageWindow.Append["Can't do it.", TRUE]; MessageWindow.Blink[]; }; }; Reset[writer]; }; WriteViewer: PUBLIC PROC [writer: Writer, viewer: ViewerClasses.Viewer] ~ { FinishOff[writer]; IF viewer # NIL AND viewer.data # NIL AND viewer.class.set # NIL THEN { viewer.class.set[viewer, writer.root, FALSE, $TiogaDocument]; writer.root _ NIL; ViewerOps.PaintViewer[viewer, all]; }; Reset[writer]; }; WriteReader: PUBLIC PROC [writer: Writer] RETURNS [reader: Reader] ~ { FinishOff[writer]; reader _ FromNode[writer.first]; writer.root _ NIL; Reset[writer]; }; Reset: PUBLIC PROC [writer: Writer] ~ { IF writer.root # NIL THEN { TEditInput.FreeTree[writer.root]; writer.root _ NIL; }; writer.first _ writer.last _ NIL; writer.lastLevel _ 0; writer.nodeInfo _ [format: NIL, comment: FALSE, level: 0, props: NIL]; writer.newRunLooks _ ALL[FALSE]; writer.newRunSize _ 0; }; finalizationQueue: SafeStorage.FinalizationQueue _ SafeStorage.NewFQ[]; FinalizationProcess: PROCEDURE ~ { DO WITH SafeStorage.FQNext[finalizationQueue] SELECT FROM reader: Reader => DoneWith[reader]; writer: Writer => Reset[writer]; ENDCASE => NULL; ENDLOOP; }; TRUSTED { SafeStorage.EstablishFinalization[ReaderRep.CODE, 0, finalizationQueue ! SafeStorage.CantEstablishFinalization => GOTO Exit]; SafeStorage.EstablishFinalization[WriterRep.CODE, 0, finalizationQueue ! SafeStorage.CantEstablishFinalization => GOTO Exit]; Process.Detach[FORK FinalizationProcess]; EXITS Exit => NULL; }; END.