EditToolSortImpl.mesa
Copyright © 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
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: BOOLFALSE;
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: BOOLEANTRUE;
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.STREAMIO.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: BOOLFALSE, 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];
DKW: Note that EliminateDuplicates might delete start!
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
move next of list to after first of list
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: BOOLFALSE;
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
Quick search looking only at the rope
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
Do this anyway in case character sets are present.
[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: BOOLFALSE;
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 CHARALL['\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𡤊i.firstChars[i])=(bc𡤋i.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𡤊scii.Lower[Rope.Fetch[aRope, a0+match]]) = (bc𡤊scii.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 {
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.
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.
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.