<> <> <> <> <> <<>> 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. <> <<>> <>