<> <> <> <> <> <<>> DIRECTORY Atom USING [DottedPairNode, GetPName, PropList], EditSpan USING [CannotDoEdit, Transpose], EditSpanSupport USING [CopySpan], FS USING [ErrorGroup, OpenFile], IO USING [PutChar, RopeFromROS, ROS, STREAM], LooksReader USING [Body, BumpIndex, FreeLooksReader, Get, GetLooksReader, Peek, Ref, SetIndex, SetPosition], MessageWindow USING [Append, Blink], NodeProps USING [CopyInfo, DoSpecs, GetProp, GetSpecs, MapProps, PutProp], NodeStyleOps USING [StyleNameForNode], 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], Rosary USING [Concat, FromItem, ROSARY, Size, Substr], SafeStorage USING [CantEstablishFinalization, EnableFinalization, EstablishFinalization, FinalizationQueue, FQNext, NewFQ], TEditDocument USING [Selection, TEditDocumentData], TEditInput USING [currentEvent, FreeTree], TEditInputOps USING [CallWithLocks, Delete], TEditSelection USING [GetSelectionGrain, MakeSelection], TextEdit USING [ChangeStyle, CharSet, Fetch, GetCharPropList], TextLooks USING [Concat, CreateRun, Looks, Runs, Substr], TextNode USING [FirstChild, Forward, LastLocWithin, LastWithin, Level, Location, LocOffset, LocWithin, MakeNodeLoc, MakeNodeSpan, NewTextNode, NodeItself, Parent, Ref, Root, Span], TiogaAccess USING [CharSet, TiogaChar], TiogaAccessPrivate USING [Reader, ReaderRep, ropePieces, Writer, WriterRep], ViewerClasses USING [Viewer], ViewerOps USING [PaintViewer]; TiogaAccessImpl: CEDAR PROGRAM IMPORTS Atom, EditSpan, EditSpanSupport, IO, LooksReader, MessageWindow, NodeProps, NodeStyleOps, Process, PutGet, Rope, RopeReader, Rosary, SafeStorage, TEditInput, TEditInputOps, TEditSelection, TextEdit, TextLooks, TextNode, ViewerOps EXPORTS TiogaAccess ~ BEGIN OPEN TiogaAccess; ROPE: TYPE ~ Rope.ROPE; ROSARY: TYPE ~ Rosary.ROSARY; Reader: TYPE ~ TiogaAccessPrivate.Reader; ReaderRep: PUBLIC TYPE ~ TiogaAccessPrivate.ReaderRep; ropePieces: NAT ~ TiogaAccessPrivate.ropePieces; textBufSize: NAT _ (574-SIZE[TEXT[0]])*2; <> Error: PUBLIC ERROR [group: FS.ErrorGroup, expl: ROPE] ~ CODE; GetExternalProp: PUBLIC PROC [key: ATOM, value: REF] RETURNS [ROPE] ~ { RETURN [NodeProps.GetSpecs[key, value]] }; GetInternalProp: PUBLIC PROC [key: ATOM, value: ROPE] RETURNS [REF] ~ { RETURN [NodeProps.DoSpecs[key, value]] }; MakePropList: PROC [textNode: TextNode.Ref] RETURNS [props: Atom.PropList _ NIL] ~ { Action: PROC [name: ATOM, value: REF] RETURNS [BOOLEAN _ FALSE] ~ { IF name = $CharSets OR name = $CharProps THEN RETURN; value _ NodeProps.CopyInfo[name: name, value: value]; IF value # NIL THEN { props _ CONS[NEW[Atom.DottedPairNode _ [key: name, val: value]], props]; }; }; [] _ NodeProps.MapProps[n: textNode, action: Action, formatFlag: FALSE, commentFlag: FALSE]; }; StartNewNode: PROC [reader: Reader, offset: INT _ 0] ~ { IF reader.textNode = NIL THEN { reader.nodeLength _ 0; reader.format _ NIL; reader.comment _ FALSE; reader.nodeProps _ NIL; RopeReader.SetPosition[reader.ropeReader, NIL, 0]; LooksReader.SetPosition[reader.looksReader, NIL, 0]; } ELSE { t: TextNode.Ref ~ reader.textNode; reader.format _ t.formatName; reader.nodeProps _ MakePropList[t]; reader.nodeLength _ Rope.Length[t.rope]; reader.comment _ t.comment; RopeReader.SetPosition[reader.ropeReader, t.rope, offset]; LooksReader.SetPosition[reader.looksReader, t.runs, offset]; }; }; InitIndex: PROC [reader: Reader] ~ { reader.textNodeIndex _ TextNode.LocOffset[loc1: [reader.root, 0], loc2: [reader.textNode, 0], skipCommentNodes: FALSE]; }; FromNode: PROC [textNode: TextNode.Ref, offset: INT _ INT.FIRST, style: ROPE _ NIL] RETURNS [Reader] ~ { new: Reader ~ NEW[ReaderRep]; new.root _ TextNode.Root[textNode]; IF style # NIL THEN TextEdit.ChangeStyle[new.root, style]; new.textNode _ textNode; new.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]]; }; TakeNodeSubstr: PROC [t: TextNode.Ref, start: INT, length: INT] = { t.rope _ Rope.Substr[t.rope, start, length]; t.runs _ TextLooks.Substr[t.runs, start, length]; IF t.hascharsets THEN { WITH NodeProps.GetProp[t, $CharSets] SELECT FROM r: ROSARY => { NodeProps.PutProp[t, $CharSets, Rosary.Substr[r, start, length]]; }; ENDCASE => NULL; }; IF t.hascharprops THEN { WITH NodeProps.GetProp[t, $CharProps] SELECT FROM r: ROSARY => { NodeProps.PutProp[t, $CharProps, Rosary.Substr[r, start, length]]; }; ENDCASE => NULL; }; }; FromSelection: PUBLIC PROC RETURNS [Reader] ~ { textNode: TextNode.Ref _ NIL; myOffset: INT _ 0; style: Rope.ROPE _ NIL; CopySelection: PROC [root: TextNode.Ref, tSel: TEditDocument.Selection] ~ { style _ Atom.GetPName[NodeStyleOps.StyleNameForNode[root]]; 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 AND copy.end.node # NIL THEN { TakeNodeSubstr[copy.end.node, 0, copy.end.where+1]; }; IF copy.start.where > 0 THEN { TakeNodeSubstr[copy.start.node, copy.start.where, INT.LAST]; copy.start.where _ 0; }; textNode _ copy.start.node; }; }; TEditInputOps.CallWithLocks[CopySelection, read]; RETURN [FromNode[textNode, myOffset, style]] }; 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 [TiogaChar] ~ { IF reader.putback # NIL THEN { t: LIST OF TiogaChar ~ reader.putback; reader.putback _ t.rest; t.rest _ NIL; RETURN [t.first]; } ELSE IF reader.end THEN {ERROR Error[client, "Attempt to read past end of document"]} ELSE IF RopeReader.GetIndex[reader.ropeReader] = reader.nodeLength THEN { t: TiogaChar _ [ charSet: 0, char: '\n, looks: ALL[FALSE], format: reader.format, comment: reader.comment, endOfNode: TRUE, deltaLevel: 0, propList: reader.nodeProps ]; t.deltaLevel _ SkipToNextNode[reader]; RETURN [t]; } ELSE { offset: INT ~ reader.ropeReader.GetIndex; t: TiogaChar ~ [ charSet: ( IF reader.textNode.hascharsets THEN TextEdit.Fetch[reader.textNode, offset].charSet ELSE 0), char: RopeReader.Get[reader.ropeReader], looks: LooksReader.Get[reader.looksReader], format: reader.format, comment: reader.comment, endOfNode: FALSE, deltaLevel: 0, propList: ( IF reader.textNode.hascharprops THEN TextEdit.GetCharPropList[reader.textNode, offset] ELSE NIL) ]; RETURN [t] }; }; 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]; }; GetNodeProps: PUBLIC PROC [reader: Reader] RETURNS [Atom.PropList] ~ { RETURN [reader.nodeProps] }; Peek: PUBLIC PROC [reader: Reader] RETURNS [TiogaChar] ~ { IF reader.putback # NIL THEN { RETURN [reader.putback.first]; } ELSE IF RopeReader.GetIndex[reader.ropeReader] = reader.nodeLength THEN { t: TiogaChar _ [ charSet: 0, char: '\n, looks: ALL[FALSE], format: reader.format, comment: reader.comment, endOfNode: TRUE, deltaLevel: TextNode.Level[reader.textNode.next]-TextNode.Level[reader.textNode], propList: reader.nodeProps ]; RETURN [t]; } ELSE { offset: INT ~ reader.ropeReader.GetIndex; t: TiogaChar ~ [ charSet: ( IF reader.textNode.hascharsets THEN TextEdit.Fetch[reader.textNode, offset].charSet ELSE 0), char: RopeReader.Peek[reader.ropeReader], looks: LooksReader.Peek[reader.looksReader], format: reader.format, comment: reader.comment, endOfNode: FALSE, deltaLevel: 0, propList: ( IF reader.textNode.hascharprops THEN TextEdit.GetCharPropList[reader.textNode, offset] ELSE NIL) ]; RETURN [t]; }; }; PeekRope: PUBLIC PROC [reader: Reader] RETURNS [rope: ROPE _ NIL] ~ { IF reader.putback # NIL THEN { <> s: IO.STREAM _ IO.ROS[]; FOR t: LIST OF TiogaChar _ reader.putback, t.rest UNTIL t = NIL DO IF t.first.endOfNode THEN RETURN [IO.RopeFromROS[s]]; IO.PutChar[s, t.first.char]; 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] ~ { t: TextNode.Ref ~ reader.textNode; start: INT ~ reader.ropeReader.GetIndex; end: INT ~ Rope.Length[t.rope]; IF t.runs # NIL THEN runs _ TextLooks.Substr[t.runs, start, end-start]; }; PeekCharSets: PROC [reader: Reader] RETURNS [charSets: ROSARY] ~ { t: TextNode.Ref ~ reader.textNode; start: INT ~ reader.ropeReader.GetIndex; end: INT ~ Rope.Length[t.rope]; IF t.hascharsets THEN charSets _ Rosary.Substr[NARROW[NodeProps.GetProp[t, $CharSets]], start, end-start] ELSE charSets _ Rosary.FromItem[NIL, end-start]; }; PeekCharProps: PROC [reader: Reader] RETURNS [charProps: ROSARY] ~ { t: TextNode.Ref ~ reader.textNode; start: INT ~ reader.ropeReader.GetIndex; end: INT ~ Rope.Length[t.rope]; IF t.hascharprops THEN charProps _ Rosary.Substr[NARROW[NodeProps.GetProp[t, $CharProps]], start, end-start] ELSE charProps _ Rosary.FromItem[NIL, end-start]; }; SkipToNextNode: PUBLIC PROC [reader: Reader] RETURNS [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.level _ reader.level + deltaLevel; reader.end _ (reader.level = 0); IF reader.end THEN NULL ELSE StartNewNode[reader]; }; PutBack: PUBLIC PROC [reader: Reader, tiogaChar: TiogaChar] ~ { reader.putback _ CONS[tiogaChar, reader.putback]; }; DiscardPutBack: PROC [reader: Reader] ~ { UNTIL reader.putback = NIL DO t: LIST OF TiogaChar _ 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.format _ NIL; reader.nodeProps _ NIL; DiscardPutBack[reader]; }; }; Writer: TYPE ~ TiogaAccessPrivate.Writer; WriterRep: PUBLIC TYPE ~ TiogaAccessPrivate.WriterRep; Create: PUBLIC PROC RETURNS [Writer] ~ { new: Writer _ NEW[WriterRep]; new.textBuf _ NEW[TEXT[textBufSize]]; Reset[new]; SafeStorage.EnableFinalization[new]; RETURN [new]; }; InsertRoot: PROC [writer: Writer, x: TextNode.Ref] ~ { IF Rope.Length[x.rope] # 0 THEN Error[client, "Root may not contain text"]; x.last _ TRUE; writer.root _ x; writer.last _ x; writer.lastLevel _ 0; }; CreateOneMoreLevel: PROC [writer: Writer] ~ { IF writer.root = NIL THEN { InsertRoot[writer, TextNode.NewTextNode[]]; writer.level _ writer.level + 1; } ELSE { new: TextNode.Ref _ TextNode.NewTextNode[]; new^ _ writer.root^; writer.root.props _ NIL; writer.root.hasstyledef _ writer.root.hasprefix _ writer.root.haspostfix _ writer.root.hascharprops _ writer.root.hascharsets _ writer.root.hasartwork _ FALSE; new.last _ TRUE; new.child _ writer.root; new.next _ NIL; writer.root.next _ new; writer.root _ new; writer.lastLevel _ writer.lastLevel + 1; writer.level _ writer.level + 1; }; }; 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.newLooksRepeat > 0 THEN writer.runs _ TextLooks.Concat[base: writer.runs, rest: TextLooks.CreateRun[writer.newLooksRepeat, writer.newLooks], baseLen: writer.nodeSize-writer.newLooksRepeat, restLen: writer.newLooksRepeat]; writer.newLooksRepeat _ 0; writer.newLooks _ ALL[FALSE]; }; GetRuns: PROC [writer: Writer] RETURNS [runs: TextLooks.Runs] ~ { FoldRuns[writer]; runs _ writer.runs; writer.runs _ NIL; }; FoldCharSets: PROC [writer: Writer] ~ { IF writer.newCharSetRepeat > 0 THEN { IF writer.charSets = NIL AND writer.newCharSet = NIL THEN NULL ELSE IF writer.charSets = NIL THEN { writer.charSets _ Rosary.Concat[Rosary.FromItem[NIL, writer.nodeSize-writer.newCharSetRepeat], Rosary.FromItem[writer.newCharSet, writer.newCharSetRepeat]]; } ELSE { writer.charSets _ Rosary.Concat[writer.charSets, Rosary.FromItem[writer.newCharSet, writer.newCharSetRepeat]]; }; }; writer.newCharSet _ NIL; writer.newCharSetRepeat _ 0; }; GetCharSets: PROC [writer: Writer] RETURNS [charSets: ROSARY] ~ { FoldCharSets[writer]; charSets _ writer.charSets; writer.charSets _ NIL; writer.newCharSet _ NIL; writer.newCharSetRepeat _ 0; IF Rosary.Size[charSets] = 0 THEN charSets _ NIL; }; FoldCharProps: PROC [writer: Writer] ~ { IF writer.newCharPropRepeat > 0 THEN { IF writer.charProps = NIL AND writer.newCharProp = NIL THEN NULL ELSE IF writer.charProps = NIL THEN { writer.charProps _ Rosary.Concat[Rosary.FromItem[NIL, writer.nodeSize-writer.newCharPropRepeat], Rosary.FromItem[writer.newCharProp, writer.newCharPropRepeat]]; } ELSE { writer.charProps _ Rosary.Concat[writer.charProps, Rosary.FromItem[writer.newCharProp, writer.newCharPropRepeat]]; }; }; writer.newCharProp _ NIL; writer.newCharPropRepeat _ 0; }; GetCharProps: PROC [writer: Writer] RETURNS [charProps: ROSARY] ~ { FoldCharProps[writer]; charProps _ writer.charProps; writer.charProps _ NIL; writer.newCharProp _ NIL; writer.newCharPropRepeat _ 0; IF Rosary.Size[charProps] = 0 THEN charProps _ NIL; }; Put: PUBLIC PROC [writer: Writer, tiogaChar: TiogaChar] ~ { IF tiogaChar.endOfNode THEN { new: TextNode.Ref _ TextNode.NewTextNode[]; new.formatName _ tiogaChar.format; new.comment _ tiogaChar.comment; new.rope _ GetRope[writer]; new.runs _ GetRuns[writer]; FOR l: Atom.PropList _ tiogaChar.propList, l.rest UNTIL l = NIL DO key: ATOM ~ NARROW[l.first.key]; NodeProps.PutProp[new, key, l.first.val]; ENDLOOP; NodeProps.PutProp[new, $CharProps, GetCharProps[writer]]; NodeProps.PutProp[new, $CharSets, GetCharSets[writer]]; IF writer.root = NIL AND writer.level=0 AND tiogaChar.deltaLevel=1 AND Rope.Length[new.rope] = 0 THEN InsertRoot[writer, new] ELSE { WHILE writer.level < 1 DO CreateOneMoreLevel[writer]; ENDLOOP; WHILE writer.level < writer.lastLevel DO writer.last _ TextNode.Parent[writer.last]; writer.lastLevel _ writer.lastLevel - 1; ENDLOOP; IF writer.level = writer.lastLevel THEN InsertSibling[writer, new] ELSE IF writer.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.nodeSize _ 0; IF writer.lastLevel # writer.level THEN ERROR; writer.level _ writer.lastLevel + tiogaChar.deltaLevel; } ELSE { text: REF TEXT ~ writer.textBuf; IF tiogaChar.looks # writer.newLooks THEN { FoldRuns[writer]; writer.newLooks _ tiogaChar.looks; }; writer.newLooksRepeat _ writer.newLooksRepeat + 1; IF NOT SameCharSet[tiogaChar.charSet, writer.newCharSet] THEN { FoldCharSets[writer]; IF tiogaChar.charSet # 0 THEN writer.newCharSet _ NEW[CharSet _ tiogaChar.charSet]; }; writer.newCharSetRepeat _ writer.newCharSetRepeat + 1; IF tiogaChar.propList # writer.newCharProp THEN { FoldCharProps[writer]; writer.newCharProp _ tiogaChar.propList; }; writer.newCharPropRepeat _ writer.newCharPropRepeat + 1; IF text.length = text.maxLength THEN FoldText[writer]; text[(text.length _ text.length+1)-1] _ tiogaChar.char; writer.nodeSize _ writer.nodeSize + 1; }; }; SameCharSet: PROC [a: CharSet, b: REF CharSet] RETURNS [BOOL] ~ INLINE { IF b = NIL THEN RETURN [a=0] ELSE RETURN [a=b^] }; 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] RETURNS [nodeEnd: BOOLEAN] ~ { textRope: ROPE _ NIL; textRuns: TextLooks.Runs _ NIL; textCharSets: ROSARY _ NIL; textCharProps: ROSARY _ NIL; textSize: INT _ 0; WHILE maxLength > 0 AND reader.putback # NIL DO tchar: TiogaChar _ Get[reader]; Put[writer, tchar]; maxLength _ maxLength - 1; IF tchar.endOfNode THEN RETURN [TRUE]; ENDLOOP; FoldText[writer]; FoldRuns[writer]; FoldCharSets[writer]; FoldCharProps[writer]; textRope _ PeekRope[reader]; textSize _ Rope.Length[textRope]; textRuns _ PeekRuns[reader]; IF writer.charSets # NIL OR reader.textNode.hascharsets THEN textCharSets _ PeekCharSets[reader]; IF writer.charProps # NIL OR reader.textNode.hascharprops THEN textCharProps _ PeekCharProps[reader]; nodeEnd _ textSize < maxLength; IF NOT nodeEnd THEN { textRope _ Rope.Substr[textRope, 0, maxLength]; textSize _ Rope.Length[textRope]; IF textRuns # NIL THEN textRuns _ TextLooks.Substr[textRuns, 0, textSize]; IF textCharSets # NIL THEN textCharSets _ Rosary.Substr[textCharSets, 0, textSize]; IF textCharProps # NIL THEN textCharSets _ Rosary.Substr[textCharProps, 0, textSize]; }; FoldRope[writer, textRope]; writer.runs _ TextLooks.Concat[base: writer.runs, rest: textRuns, baseLen: writer.nodeSize, restLen: textSize]; IF textCharSets # NIL AND writer.charSets = NIL THEN { writer.charSets _ Rosary.FromItem[NIL, writer.nodeSize]; }; textCharSets _ Rosary.Concat[writer.charSets, textCharSets]; IF textCharProps # NIL AND writer.charProps = NIL THEN { writer.charProps _ Rosary.FromItem[NIL, writer.nodeSize]; }; textCharProps _ Rosary.Concat[writer.charProps, textCharProps]; RopeReader.BumpIndex[reader.ropeReader, textSize]; LooksReader.BumpIndex[reader.looksReader, textSize]; writer.nodeSize _ writer.nodeSize + textSize; IF nodeEnd THEN Put[writer, Get[reader]]; }; Nest: PUBLIC PROC [writer: Writer, delta: INT] ~ { level: INT ~ writer.level + delta; IF level > writer.lastLevel+1 THEN Error[client, "Nesting level may not increase by more than one"]; writer.level _ level; }; 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 _ MAX[tSel.granularity, char]; } ELSE { <> 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] ~ { RETURN [Rope.Length[textNode.rope]]; }; FinishOff: PROC [writer: Writer] ~ { <> IF writer.ropes#ALL[NIL] OR writer.textBuf.length>0 THEN Put[writer, [ charSet: 0, char: '\n, looks: ALL[FALSE], format: NIL, comment: FALSE, endOfNode: TRUE, deltaLevel: 0, propList: NIL ]]; }; 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, 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.level _ 0; writer.lastLevel _ 0; writer.nodeSize _ 0; writer.ropes _ ALL[NIL]; writer.textBuf.length _ 0; writer.runs _ NIL; writer.newLooks _ ALL[FALSE]; writer.newLooksRepeat _ 0; writer.charSets _ NIL; writer.newCharSet _ NIL; writer.newCharSetRepeat _ 0; writer.charProps _ NIL; writer.newCharProp _ NIL; writer.newCharPropRepeat _ 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.