DIRECTORY Ascii USING [Lower], Basics USING [CompareCard, CompareINT, Comparison], Buttons USING [ButtonProc], EditSpan USING [Delete, Move], EditToolBuilder USING [BuildButton, BuildPair, BuildTriple, ToMiddle, ToNext], EditToolPrivate USING [ChangeState, CheckPSel, CycleTriple, DoButton, Info, mainToolInfo, Register, sortBranches, sortLines, sortText, tSel], IO USING [Put, RopeFromROS, ROS, STREAM], Labels USING [Set], List USING [Sort, DReverse], MessageWindow USING [Append, Blink], Rope USING [AppendChars, Fetch, ROPE, Size], RopeEdit USING [BlankChar, PunctuationChar], RuntimeError USING [UNCAUGHT], TEditDocument USING [Selection], TEditInput USING [CommandProc, CurrentEvent], TEditInputOps USING [CheckReadonly], TEditLocks USING [Lock], TEditOps USING [GetSelData], TEditSelection USING [LockSel, MakeSelection, SelectionRoot, UnlockDocAndPSel, UnlockSel], TextEdit USING [DeleteText, FetchChar, FetchLooks, InsertChar, ReplaceByChar, ReplaceText, Size], TextLooks USING [Looks], TextNode USING [LastWithin, MakeNodeLoc, MakeNodeSpan, NewTextNode, Next, NodeItself, Offset, Parent, Previous, Ref, RefTextNode, Root, Span], UndoEvent USING [Ref]; EditToolSortImpl: CEDAR PROGRAM IMPORTS Ascii, Basics, EditSpan, EditToolBuilder, EditToolPrivate, IO, Labels, List, MessageWindow, Rope, RopeEdit, RuntimeError, TEditInput, TEditInputOps, TEditLocks, TEditOps, TEditSelection, TextEdit, TextNode EXPORTS EditToolPrivate = BEGIN ROPE: TYPE = Rope.ROPE; Offset: TYPE = TextNode.Offset; BuildSortButtons: PUBLIC PROC [info: EditToolPrivate.Info] = { OPEN info; [] _ EditToolBuilder.BuildButton[layout, "Sort", DoSort, info, TRUE]; [] _ EditToolBuilder.BuildButton[layout, "Reverse", DoReverse, info, TRUE]; [] _ EditToolBuilder.BuildButton[layout, "Sort-and-remove-duplicates", DoSortElim, info, TRUE]; EditToolBuilder.ToNext[layout]; sortIncreasing _ TRUE; [sortOrderLabel,] _ EditToolBuilder.BuildPair[layout,SortOrderButton,sortIncreasing,sortIncRope,sortDecRope,info]; EditToolBuilder.ToMiddle[layout]; sortKind _ EditToolPrivate.sortText; [sortKindLabel,] _ EditToolBuilder.BuildTriple[layout, SortKindButton, sortKind, sortTextRope, sortLinesRope, sortBranchesRope, info]; }; sortIncRope: ROPE = "Sort increasing"; sortDecRope: ROPE = "Sort decreasing"; sortIncAtom: LIST OF REF = EditToolPrivate.Register[$SortIncreasing,SortIncOp]; sortDecAtom: LIST OF REF = EditToolPrivate.Register[$SortDecreasing,SortDecOp]; SortOrderButton: Buttons.ButtonProc = { EditToolPrivate.ChangeState[EditToolPrivate.mainToolInfo.sortIncreasing, sortIncAtom,sortDecAtom] }; SortIncOp: TEditInput.CommandProc = { SortInc[EditToolPrivate.mainToolInfo] }; SortInc: PROC [info: EditToolPrivate.Info] = { OPEN info; sortIncreasing _ TRUE; Labels.Set[sortOrderLabel,sortIncRope] }; SortDecOp: TEditInput.CommandProc = { SortDec[EditToolPrivate.mainToolInfo] }; SortDec: PROC [info: EditToolPrivate.Info] = { OPEN info; sortIncreasing _ FALSE; Labels.Set[sortOrderLabel,sortDecRope] }; sortTextRope: ROPE = "Text (blanks delimit)"; sortLinesRope: ROPE = "Lines (CRs delimit)"; sortBranchesRope: ROPE = "Branches"; sortTextAtom: LIST OF REF = EditToolPrivate.Register[$SortText,SortTextOp]; sortLinesAtom: LIST OF REF = EditToolPrivate.Register[$SortLines,SortLinesOp]; sortBranchesAtom: LIST OF REF = EditToolPrivate.Register[$SortBranches,SortBranchesOp]; SortKindButton: Buttons.ButtonProc = { EditToolPrivate.CycleTriple[EditToolPrivate.mainToolInfo.sortKind, sortTextAtom, sortLinesAtom, sortBranchesAtom] }; SortTextOp: TEditInput.CommandProc = { SortText[EditToolPrivate.mainToolInfo] }; SortText: PROC [info: EditToolPrivate.Info] = { OPEN info; sortKind _ EditToolPrivate.sortText; Labels.Set[sortKindLabel,sortTextRope] }; SortLinesOp: TEditInput.CommandProc = { SortLines[EditToolPrivate.mainToolInfo] }; SortLines: PROC [info: EditToolPrivate.Info] = { OPEN info; sortKind _ EditToolPrivate.sortLines; Labels.Set[sortKindLabel,sortLinesRope] }; SortBranchesOp: TEditInput.CommandProc = { SortBranches[EditToolPrivate.mainToolInfo] }; SortBranches: PROC [info: EditToolPrivate.Info] = { OPEN info; sortKind _ EditToolPrivate.sortBranches; Labels.Set[sortKindLabel,sortBranchesRope] }; doSortAtom: LIST OF REF = EditToolPrivate.Register[$DoSort,DoSortOp]; DoSort: Buttons.ButtonProc = { EditToolPrivate.DoButton[doSortAtom] }; DoSortOp: TEditInput.CommandProc = { DoSortCom[EditToolPrivate.mainToolInfo,FALSE,FALSE] }; doSortAndElimAtom: LIST OF REF = EditToolPrivate.Register[$DoSortAndRemoveDuplicates,DoSortAndElimOp]; DoSortElim: Buttons.ButtonProc = { EditToolPrivate.DoButton[doSortAndElimAtom] }; DoSortAndElimOp: TEditInput.CommandProc = { DoSortCom[EditToolPrivate.mainToolInfo,FALSE,TRUE] }; doReverseAtom: LIST OF REF = EditToolPrivate.Register[$DoReverse,DoReverseOp]; DoReverse: Buttons.ButtonProc = { EditToolPrivate.DoButton[doReverseAtom] }; DoReverseOp: TEditInput.CommandProc = { DoSortCom[EditToolPrivate.mainToolInfo,TRUE,FALSE] }; CountBlanks: PROC [rope: Rope.ROPE, from: Offset] RETURNS [count: Offset] = { size: INT ~ Rope.Size[rope]; count _ from; WHILE count < size AND RopeEdit.BlankChar[Rope.Fetch[rope, count]] DO count _ count+1; ENDLOOP; count _ count-from; }; DoSortCom: PROC [info: EditToolPrivate.Info, reversing, eliminateDuplicates: BOOL] = { sel: TEditDocument.Selection; broken: BOOL _ FALSE; event: UndoEvent.Ref _ TEditInput.CurrentEvent[]; TEditSelection.LockSel[primary, "DoSortCom"]; sel _ TEditOps.GetSelData[]; IF NOT (EditToolPrivate.CheckPSel[sel] AND TEditInputOps.CheckReadonly[sel]) THEN { MessageWindow.Append["Make selection.", TRUE]; MessageWindow.Blink; TEditSelection.UnlockSel[primary]; } ELSE { root: TextNode.Ref _ TEditSelection.SelectionRoot[sel]; quit: BOOLEAN _ TRUE; count: INT _ 0; duplicates: INT _ 0; EditToolPrivate.tSel^ _ sel^; info.interrupt^ _ FALSE; [ ] _ TEditLocks.Lock[root, "DoSortCom"]; [quit, count, duplicates] _ DoSortComI[info, reversing, eliminateDuplicates, root, event ! RuntimeError.UNCAUGHT => { broken _ TRUE; TEditSelection.UnlockDocAndPSel[root]; REJECT }]; IF NOT broken THEN { EditToolPrivate.tSel.pendingDelete _ FALSE; TEditSelection.MakeSelection[new: EditToolPrivate.tSel]; TEditSelection.UnlockDocAndPSel[root]; IF NOT info.interrupt^ THEN { h: IO.STREAM _ IO.ROS[]; IO.Put[h, [integer[count]], IF ~reversing THEN [rope[" items sorted. "]] ELSE [rope[" items reversed. "]]]; IF eliminateDuplicates THEN IO.Put[h, [integer[duplicates]], IF duplicates=1 THEN [rope[" duplicate eliminated. "]] ELSE [rope[" duplicates eliminated. "]]]; MessageWindow.Append[IO.RopeFromROS[h],TRUE] } ELSE MessageWindow.Append["interrupted",TRUE]; }; }; }; DoSortComI: PROC [info: EditToolPrivate.Info, reversing, eliminateDuplicates: BOOL, root: TextNode.Ref, event: UndoEvent.Ref] RETURNS [quit: BOOL _ FALSE, count: INT _ 0, duplicates: INT _ 0] = { span: TextNode.Span; list: LIST OF REF; kind: [0..2] _ info.sortKind; textBuf: REF TEXT ~ NEW[TEXT[textBufSize]]; GetFirstChars: PROC [rope: ROPE, start, len: INT] RETURNS [firstChars: FirstChars] ~ { textBuf.length _ 0; [] _ Rope.AppendChars[textBuf, rope, start, MIN[len, nFirstChars]]; FOR i: NAT IN [0..textBuf.length) DO firstChars[i] _ Ascii.Lower[textBuf[i]]; ENDLOOP; }; span _ [EditToolPrivate.tSel.start.pos, EditToolPrivate.tSel.end.pos]; IF kind=EditToolPrivate.sortBranches THEN { start, end, last, loc: TextNode.Ref; IsSibling: PROC [first, last: TextNode.Ref] RETURNS [BOOL] = { FOR n: TextNode.Ref _ first, TextNode.Next[n] DO SELECT n FROM NIL => RETURN [FALSE]; last => EXIT; ENDCASE; ENDLOOP; RETURN [TRUE] }; start _ span.start.node; FOR n: TextNode.Ref _ span.end.node, TextNode.Parent[n] DO IF n = NIL THEN { MessageWindow.Append["Selection must end inside a sibling of start node.",TRUE]; MessageWindow.Blink[]; RETURN [quit: TRUE] }; IF IsSibling[start,n] THEN { last _ n; EXIT }; ENDLOOP; loc _ start; end _ TextNode.Next[last]; UNTIL loc=end DO -- make list of things to sort node: TextNode.RefTextNode _ loc; blanks: Offset _ CountBlanks[node.rope,0]; list _ CONS[NEW[ItemRep _ [node, 0, TextEdit.Size[node], blanks, GetFirstChars[node.rope, blanks, LAST[INT]]]], list]; loc _ TextNode.Next[loc]; count _ count+1; ENDLOOP; IF NOT reversing AND NOT info.interrupt^ THEN { list _ List.Sort[list, Compare]; IF NOT info.sortIncreasing THEN list _ List.DReverse[list]; }; IF NOT info.interrupt^ THEN { -- reorder the branches Del: PROC [text: TextNode.RefTextNode] = { [] _ EditSpan.Delete[ root: root, del: TextNode.MakeNodeSpan[text,TextNode.LastWithin[text]], event: event, saveForPaste: FALSE] }; parent: TextNode.Ref _ TextNode.Parent[start]; previous: TextNode.Ref _ TextNode.Previous[start, parent]; IF eliminateDuplicates THEN [duplicates, ----] _ EliminateDuplicates[list, Del]; start _ NARROW[list.first, Item].text; -- new start IF TextNode.Previous[start] # previous THEN { -- move start to correct location IF previous=NIL THEN -- move first to child of parent [] _ EditSpan.Move[ destRoot: root, sourceRoot: root, dest: TextNode.MakeNodeLoc[parent], source: TextNode.MakeNodeSpan[start, TextNode.LastWithin[start]], where: child, event: event] ELSE -- move first to after node before start [] _ EditSpan.Move[ destRoot: root, sourceRoot: root, dest: TextNode.MakeNodeLoc[previous], source: TextNode.MakeNodeSpan[start, TextNode.LastWithin[start]], where: sibling, event: event]; }; FOR lst: LIST OF REF _ list, lst.rest DO next, dest: TextNode.Ref; nesting: INTEGER; lstFirst: Item ~ NARROW[lst.first]; lstNxt: Item ~ IF lst.rest=NIL THEN NIL ELSE NARROW[lst.rest.first]; IF lst.rest=NIL THEN { last _ lstFirst.text; EXIT }; next _ lstNxt.text; IF lstFirst.text.next = next THEN LOOP; -- already in correct order dest _ TextNode.LastWithin[lstFirst.text]; nesting _ 0; FOR n: TextNode.Ref _ dest, TextNode.Parent[n] UNTIL n=lstFirst.text DO nesting _ nesting-1; ENDLOOP; [] _ EditSpan.Move[ destRoot: root, sourceRoot: root, dest: TextNode.MakeNodeLoc[dest], source: TextNode.MakeNodeSpan[next,TextNode.LastWithin[next]], nesting: nesting, where: sibling, event: event ]; ENDLOOP; }; SELECT EditToolPrivate.tSel.granularity FROM node, branch => NULL; ENDCASE => EditToolPrivate.tSel.granularity _ node; EditToolPrivate.tSel.start.pos _ [start, 0]; last _ TextNode.LastWithin[last]; EditToolPrivate.tSel.end.pos _ [last, MAX[TextEdit.Size[last],1]-1]; } ELSE { -- sorting text/lines within a single node node: TextNode.RefTextNode ~ span.start.node; root: TextNode.RefTextNode ~ TextNode.Root[node]; rope: Rope.ROPE; start, loc, end, newSize, elim: Offset; lastCharSet, finalCharSet: NAT _ 0; lastChar, delimChar, finalChar: CHAR _ 0C; looks: TextLooks.Looks; addedChar, addedDelimiter, replacedFinalDelimiter: BOOL _ FALSE; newNode: TextNode.RefTextNode ~ MakeNewNode[]; newRoot: TextNode.RefTextNode ~ TextNode.Root[newNode]; MakeNewNode: PROC RETURNS [TextNode.RefTextNode] ~ { root: TextNode.RefTextNode _ TextNode.NewTextNode[]; new: TextNode.RefTextNode _ TextNode.NewTextNode[]; root.last _ TRUE; new.last _ TRUE; root.child _ new; new.next _ root; RETURN [new]; }; Next: PROC [at: Offset] RETURNS [next: Offset, finalCharSet: [0..256), finalChar: CHAR] = { -- scan to break char: CHAR _ '\000; charSet: NAT _ 0; rope: ROPE ~ node.rope; size: INT ~ Rope.Size[rope]; Dlm: PROC [charSet: NAT, char: CHAR] RETURNS [BOOL] _ IF kind=EditToolPrivate.sortText THEN Blnk ELSE Newl; Blnk: PROC [charSet: NAT, char: CHAR] RETURNS [BOOL] ~ {RETURN [charSet = 0 AND RopeEdit.BlankChar[char]]}; Newl: PROC [charSet: NAT, char: CHAR] RETURNS [BOOL] ~ {RETURN [charSet = 0 AND char='\n]}; next _ at; WHILE next < end DO k: NAT _ 0; textBuf.length _ 0; [] _ Rope.AppendChars[textBuf, rope, next, end-next]; UNTIL k = textBuf.length OR (kind=EditToolPrivate.sortText AND RopeEdit.BlankChar[textBuf[k]]) OR (textBuf[k]='\n) DO k _ k + 1; ENDLOOP; next _ next + k; IF k # textBuf.length THEN EXIT; ENDLOOP; WHILE next < end DO [charSet, char] _ TextEdit.FetchChar[node, next]; IF Dlm[charSet, char] THEN EXIT; next _ next + 1; ENDLOOP; finalCharSet _ charSet; finalChar _ char; WHILE next < end DO [charSet, char] _ TextEdit.FetchChar[node, next]; IF NOT Dlm[charSet, char] THEN EXIT; next _ next + 1; ENDLOOP; }; IF span.start.node # span.end.node THEN { MessageWindow.Append["Selection must be in a single node.",TRUE]; MessageWindow.Blink[]; RETURN [quit: TRUE] }; IF node=NIL THEN RETURN [quit: TRUE]; rope _ node.rope; IF (start _ span.start.where) = TextNode.NodeItself THEN start _ 0; IF (end _ span.end.where) = TextNode.NodeItself THEN end _ Rope.Size[rope]-1; IF end <= start THEN RETURN [quit: TRUE]; loc _ start; end _ MIN[end+1, Rope.Size[node.rope]]; -- location after the things to be sorted [lastCharSet, lastChar] _ TextEdit.FetchChar[node, end-1]; IF kind=EditToolPrivate.sortText THEN { IF lastCharSet#0 OR NOT RopeEdit.BlankChar[lastChar] THEN { -- add blank [] _ TextEdit.InsertChar[root: root, dest: node, destLoc: end, char: ' , event: event]; addedChar _ TRUE } } ELSE IF lastCharSet#0 OR lastChar # '\n THEN { -- add CR [] _ TextEdit.InsertChar[root: root, dest: node, destLoc: end, char: '\n , event: event]; addedChar _ TRUE }; IF addedChar THEN { end _ end+1; rope _ node.rope }; UNTIL loc=end DO -- make list of things to sort next, blanks: Offset; finalPunct: BOOL; [next, finalCharSet, finalChar] _ Next[loc]; IF info.interrupt^ THEN RETURN; finalPunct _ finalCharSet=0 AND RopeEdit.PunctuationChar[finalChar]; IF loc=start AND finalPunct THEN delimChar _ finalChar ELSE IF finalCharSet#0 OR finalChar # delimChar THEN { IF next#end THEN delimChar _ 0C -- don't have uniform delimiters ELSE IF delimChar # 0C THEN { IF finalPunct THEN { -- replace final delimiter finalCharLoc: Offset _ end; DO s: NAT; c: CHAR; IF end = 0 THEN EXIT; finalCharLoc _ finalCharLoc - 1; [s,c] _ TextEdit.FetchChar[node, finalCharLoc]; IF s = finalCharSet AND c = finalChar THEN EXIT; ENDLOOP; looks _ TextEdit.FetchLooks[node, finalCharLoc]; [] _ TextEdit.ReplaceByChar[ root: root, dest: node, start: finalCharLoc, len: 1, inherit: FALSE, looks: looks, char: delimChar, charSet: finalCharSet, event: event ]; replacedFinalDelimiter _ TRUE } ELSE { -- add delimiter to final item [] _ TextEdit.InsertChar[root: root, dest: node, destLoc: end-1, char: delimChar, event: event]; addedDelimiter _ TRUE; next _ end _ end+1 }; rope _ node.rope; }; }; blanks _ IF kind=EditToolPrivate.sortText THEN 0 ELSE CountBlanks[node.rope, loc]; list _ CONS[NEW[ItemRep_[node, loc, next-loc, blanks, GetFirstChars[node.rope, loc+blanks, next-(loc+blanks)]]], list]; loc _ next; count _ count+1; ENDLOOP; elim _ 0; IF NOT reversing THEN { IF info.interrupt^ THEN RETURN; list _ List.Sort[list, Compare]; IF info.interrupt^ THEN RETURN; IF eliminateDuplicates THEN [duplicates, elim] _ EliminateDuplicates[list]; IF info.interrupt^ THEN RETURN; IF NOT info.sortIncreasing THEN list _ List.DReverse[list]; }; UNTIL list = NIL OR info.interrupt^ DO t: LIST OF REF _ list; tFirst: Item ~ NARROW[t.first]; list _ list.rest; t.rest _ NIL; [] _ TextEdit.ReplaceText[destRoot: newRoot, sourceRoot: root, dest: newNode, destStart: Rope.Size[newNode.rope], destLen: 0, source: node, sourceStart: tFirst.start, sourceLen: tFirst.len, event: NIL]; tFirst.text _ NIL; ENDLOOP; newSize _ Rope.Size[newNode.rope]; IF NOT info.interrupt^ THEN { IF newSize # end-start-elim THEN ERROR; [] _ TextEdit.ReplaceText[destRoot: root, sourceRoot: newRoot, dest: node, destStart: start, destLen: end-start, source: newNode, sourceStart: 0, sourceLen: Rope.Size[newNode.rope], event: event] }; IF addedChar THEN { -- delete trailing blank or CR TextEdit.DeleteText[root: root, text: node, start: start+newSize-1, len: 1, event: event]; newSize _ newSize-1 }; IF addedDelimiter THEN { TextEdit.DeleteText[ root: root, text: node, start: start+newSize-1, len: 1, event: event]; newSize _ newSize-1 }; IF replacedFinalDelimiter THEN { [] _ TextEdit.ReplaceByChar[ root: root, dest: node, start: start+newSize-1, len: 1, inherit: FALSE, looks: looks, char: finalChar, charSet: finalCharSet, event: event]; }; EditToolPrivate.tSel.end.pos.where _ EditToolPrivate.tSel.start.pos.where + MAX[newSize-1, 0]; newNode.child _ NIL; }; -- end of text/line case }; textBufSize: NAT _ 24; debug: BOOL _ FALSE; EliminateDuplicates: PROC [lst: LIST OF REF, proc: PROC [TextNode.RefTextNode] _ NIL] RETURNS [number, elim: Offset] = { number _ elim _ 0; UNTIL lst.rest = NIL DO IF Compare[lst.first, lst.rest.first]=equal THEN { -- eliminate lst.rest t: LIST OF REF ~ lst.rest; tFirst: Item ~ NARROW[t.first, Item]; elim _ elim + tFirst.len; number _ number + 1; IF proc # NIL THEN proc[tFirst.text]; lst.rest _ t.rest; t.first _ NIL; t.rest _ NIL; } ELSE lst _ lst.rest; ENDLOOP; }; Item: TYPE = REF ItemRep; ItemRep: TYPE = RECORD [text: TextNode.RefTextNode, start, len, blanks: INT, firstChars: FirstChars]; nFirstChars: NAT ~ 6; FirstChars: TYPE ~ ARRAY [0..nFirstChars) OF CHAR _ ALL['\000]; Compare: PROC [ref1: REF ANY, ref2: REF ANY] RETURNS [result: Basics.Comparison] = { ai: Item ~ NARROW[ref1]; bi: Item ~ NARROW[ref2]; IF ai.firstChars # bi.firstChars THEN { ac, bc: CHAR; FOR i: NAT IN [0..nFirstChars) WHILE (ac_ai.firstChars[i])=(bc_bi.firstChars[i]) DO ENDLOOP; result _ Basics.CompareCard[ORD[ac], ORD[bc]]; } ELSE { aRope: ROPE = ai.text.rope; bRope: ROPE = bi.text.rope; aSize: INT = ai.len-ai.blanks; bSize: INT = bi.len-bi.blanks; minSize: INT = MIN[aSize, bSize]; a0: INT = ai.start+ai.blanks; b0: INT = bi.start+bi.blanks; match: INT _ 0; ac, bc: CHAR; firstCaseDifference: Basics.Comparison _ equal; WHILE match < minSize AND (ac_Rope.Fetch[aRope, a0+match])=(bc_Rope.Fetch[bRope, b0+match]) DO match _ match + 1; ENDLOOP; IF match < minSize THEN { result _ Basics.CompareCard[Ascii.Lower[ac]-'\000, Ascii.Lower[bc]-'\000]; IF result = equal THEN { firstCaseDifference _ Basics.CompareCard[ORD[ac], ORD[bc]]; WHILE match < minSize AND (ac_Ascii.Lower[Rope.Fetch[aRope, a0+match]]) = (bc_Ascii.Lower[Rope.Fetch[bRope, b0+match]]) DO match _ match + 1; ENDLOOP; }; }; IF match < minSize THEN result _ Basics.CompareCard[Ascii.Lower[ac]-'\000, Ascii.Lower[bc]-'\000] ELSE IF firstCaseDifference # equal THEN result _ firstCaseDifference ELSE result _ Basics.CompareINT[aSize, bSize]; IF result = equal AND (ai.text.hascharsets OR bi.text.hascharsets) THEN { FOR i: INT IN [0..match) WHILE result = equal DO result _ Basics.CompareCard[ TextEdit.FetchChar[ai.text, a0+i].charSet, TextEdit.FetchChar[bi.text, b0+i].charSet ]; ENDLOOP; }; }; }; END. "EditToolSortImpl.mesa Copyright c 1985 by Xerox Corporation. All rights reserved. Michael Plass, May 6, 1985 9:23:49 am PDT Doug Wyatt, March 6, 1985 11:18:06 am PST Russ Atkinson (RRA) June 18, 1985 5:04:14 pm PDT DKW: Note that EliminateDuplicates might delete start! move next of list to after first of list Quick search looking only at the rope Do this anyway in case character sets are present. Disambiguate 16-bit character codes. Note the character set codes are considered less significant for the purposes of sorting, and are not used at all unless the strings are otherwise identical. Michael Plass, October 23, 1984 2:29:35 pm PDT  Fixed bounds fault that came up when sorting lines that came up to the end of the document. Also added kludge to unlock the selection in case of an uncaught error, to make debugging easier. Michael Plass, March 20, 1985 3:55:03 pm PST  Cedar 6 conversion to handle 16-bit character codes. Also fixed kludge to unlock the selection AND DOCUMENT in case of an uncaught error, to make debugging easier. Κρ˜codešœ™Kšœ Οmœ1™žœ˜VK˜Kšœžœžœ˜K˜1K˜-K˜šžœžœ!žœ#žœ˜SKšœ(žœ˜.K˜K˜"K˜—šžœ˜K˜7Kšœžœžœ˜Kšœžœ˜Kšœ žœ˜K˜Kšœžœ˜K˜)šœhžœ˜uKšœ žœ˜Kšœ&˜&Kšž˜Kšœ˜—šžœžœžœ˜Kšœ%žœ˜+K˜8K˜&šžœžœžœ˜Kš œžœžœžœžœ˜šžœ˜Kšžœ žœ˜-Kšžœ˜#—šžœžœžœ˜žœ,žœžœžœ žœžœ ˜ΓK˜Kšœžœžœžœ˜K˜Kš œ žœžœžœžœ˜+š Ÿ œžœžœžœžœ˜VKšœ˜Kšœ,žœ˜Cšžœžœžœž˜$Kšœ(˜(Kšžœ˜—Kšœ˜—K˜Fšžœ#žœ˜,K˜$šŸ œžœžœžœ˜>šžœ+ž˜0šžœž˜ Kšžœžœžœ˜—Kšœžœ˜ Kšžœ˜Kšžœ˜—Kšžœžœ˜K˜—K˜šžœ5ž˜:Kšžœžœžœ˜KšœJžœ˜PK˜Kšžœžœ˜K˜Kšžœžœ žœ˜.Kšžœ˜—Kšœ'˜'šžœ žœΟc˜/K˜!K˜*Kš œžœžœSžœžœ ˜vKšœ˜K˜Kšžœ˜—š žœžœ žœžœžœ˜/Kšœ ˜ Kšžœžœžœ˜;K˜—šžœžœžœ ˜5šŸœžœ!˜*˜K˜GKšœžœ˜"K˜——K˜.K˜:Kšžœžœ œ#˜PKšœ6™6Kšœžœ  ˜3šžœ%žœ !˜OKšžœ žœžœ  ˜5˜K˜EK˜AK˜Kšžœ (˜-˜K˜!K˜%K˜AK˜——K˜—š žœžœžœžœž˜(Kšœ(™(K˜Kšœ žœ˜Kšœžœ ˜#Kš œžœ žœžœžœžœžœ˜DKšžœ žœžœžœ˜4Kšœ˜Kšžœžœžœ ˜CKšœ*˜*K˜ šžœ,žœž˜GKšœ˜Kšžœ˜—˜K˜K˜K˜!K˜>K˜.K˜—Kšžœ˜—K˜—šžœ"ž˜,Kšœžœ˜Kšžœ,˜3—K˜,K˜!Kšœ&žœ˜DK˜—šžœ *˜1K˜-Kšœ1˜1Kšœ žœ˜K˜'Kšœžœ˜#Kšœ žœ˜*K˜Kšœ3žœžœ˜@K˜.Kšœ7˜7šŸ œžœžœ˜4K˜4K˜3Kšœ žœ˜Kšœ žœ˜Kšœ˜Kšœ˜Kšžœ˜ Kšœ˜—š Ÿœžœžœ3žœ ˜lKšœžœ˜Kšœ žœ˜Kšœžœ ˜Kšœžœ˜š Ÿœžœ žœžœžœžœ˜3Kšœžœžœžœ˜7š Ÿœžœ žœžœžœžœ˜4Kšœžœžœ˜6—š Ÿœžœ žœžœžœžœ˜4Kšœžœžœ ˜&——K˜ šžœ ž˜K™%Kšœžœ˜ Kšœ˜Kšœ5˜5Kšžœ˜Kšžœ žœ!˜Fšžœž˜Kšœ ˜ Kšžœ˜—Kšœ˜Kšžœžœžœ˜ Kšžœ˜—šžœ ž˜K™2Kšœ1˜1Kšžœžœžœ˜ Kšœ˜Kšžœ˜—Kšœ˜Kšœ˜šžœ ž˜Kšœ1˜1Kšžœžœžœžœ˜$Kšœ˜Kšžœ˜—K˜—šžœ!žœ˜)Kšœ;žœ˜AKšœžœžœ˜+K˜—Kš žœžœžœžœžœ˜%K˜Kšžœ2žœ ˜CKšžœ.žœ˜MKšžœžœžœžœ˜)K˜ Kšœžœ  )˜RK˜:šžœžœ˜'š žœžœžœžœ  ˜HK˜XKšœ žœ˜K˜—K˜—š žœžœžœžœ  ˜8K˜YKšœ žœ˜K˜—Kšžœ žœ#˜4šžœ žœ ˜/K˜Kšœ žœ˜Kšœ,˜,Kšžœžœžœ˜Kšœžœ%˜DKšžœ žœ žœ˜6šžœžœžœžœ˜6Kšžœ žœ  ˜@šžœžœžœ˜šžœ žœ ˜/K˜šž˜Kšœžœžœ˜Kšžœ žœžœ˜K˜ K˜/Kšžœžœžœžœ˜0Kšžœ˜—Kšœ0˜0˜K˜4Kšœ žœ˜Kšœ4˜4K˜—Kšœžœ˜K˜—šžœ ˜%K˜`Kšœžœ˜*K˜—K˜K˜—Kšœ˜—Kšœ žœžœžœ˜RKšœžœžœh˜wK˜ K˜Kšžœ˜—K˜ šžœžœ žœ˜Kšžœžœžœ˜Kšœ ˜ Kšžœžœžœ˜Kšžœžœ0˜KKšžœžœžœ˜Kšžœžœžœ˜;K˜—šžœžœžœž˜&Kšœžœžœžœ˜Kšœžœ ˜Kšœ˜Kšœ žœ˜ K•StartOfExpansion[destRoot: TextEdit.RefTextNode, sourceRoot: TextEdit.RefTextNode, dest: TextEdit.RefTextNode, destStart: INT _ 0, destLen: INT _ 2147483647, source: TextEdit.RefTextNode, sourceStart: INT _ 0, sourceLen: INT _ 2147483647, event: TextEdit.Event _ NIL]šœΕžœ˜ΚKšœžœ˜Kšžœ˜—Kšœ"˜"šžœžœžœ˜Kšžœžœžœ˜'K–[destRoot: TextEdit.RefTextNode, sourceRoot: TextEdit.RefTextNode, dest: TextEdit.RefTextNode, destStart: INT _ 0, destLen: INT _ 2147483647, source: TextEdit.RefTextNode, sourceStart: INT _ 0, sourceLen: INT _ 2147483647, event: TextEdit.Event _ NIL]šœΔ˜ΔK˜—šžœ žœ ˜2K˜ZK˜K˜—šžœžœ˜˜K˜F—K˜K˜—šžœžœ˜ Kšœ˜K˜7Kšœ žœF˜TKšœ˜—KšœLžœ˜^Kšœžœ˜Kšœ ˜—K˜—K˜šœ žœ˜K˜—Kšœžœžœ˜šŸœžœžœžœžœžœžœžœ˜xK˜šžœ žœž˜šžœ*žœ ˜HKšœžœžœžœ ˜Kšœžœ˜%Kšœ˜K˜Kšžœžœžœ˜%Kšœ˜Kšœ žœ˜Kšœ žœ˜ K˜—Kšžœ˜Kšžœ˜—K˜—K˜Kšœžœžœ˜Kšœ žœžœ2žœ˜eKšœ žœ˜Kš œ žœžœžœžœžœ˜?šŸœžœžœžœžœžœžœ ˜TKšœ žœ˜Kšœ žœ˜šžœžœ˜'Kšœžœ˜ š žœžœžœžœ-ž˜SKšžœ˜—Kšœžœžœ˜.Kšœ˜—šžœ˜Kšœžœ˜Kšœžœ˜K–O[s1: ROPE, pos1: INT _ 0, s2: ROPE, pos2: INT _ 0, case: BOOL _ TRUE]šœžœ˜Kšœžœ˜Kšœ žœžœ˜!Kšœžœ˜Kšœžœ˜Kšœžœ˜Kšœžœ˜ Kšœ/˜/Kšžœ˜šžœCž˜HKšœ˜Kšžœ˜—šžœžœ˜KšœJ˜Jšžœžœ˜Kšœ)žœžœ˜;Kšžœ˜šžœ_ž˜dKšœ˜Kšžœ˜—Kšœ˜—K˜—KšžœžœJ˜aKšžœžœžœ˜EKšžœ*˜.šžœžœžœžœ˜IK™Γš žœžœžœ žœž˜0šœ˜Kšœ*˜*Kšœ)˜)Kšœ˜—Kšžœ˜—Kšœ˜—Kšœ˜—K˜—K˜Kšžœ˜K˜—Kšœο™οK™KšœΣ™Σ—…—Hbbu