-- EditToolSortImpl.mesa
-- Edited by Paxton on February 8, 1982 9:49 am

DIRECTORY
  EditToolPrivate,
  Buttons,
  Convert,
  EditNotify,
  EditSpan,
  Inline,
  IOStream,
  UndoEvent,
  Labels,
  List,
  Menus,
  MessageWindow,
  NodeAddrs,
  Rope,
  RopeEdit,
  RopeReader,
  RunReader,
  Runtime,
  TextEdit,
  TextFind,
  TextLooks,
  TextLooksSupport,
  TextNode,
  TEditDocument,
  TEditInputOps,
  TEditOps,
  TreeFind,
  UserTerminal,
  ViewerOps,
  ViewerClasses,
  ViewerMenus,
  ViewerSpecs;

EditToolSortImpl: PROGRAM

IMPORTS EditToolPrivate, Buttons, EditSpan, Labels, MessageWindow, 
	Rope, RopeEdit, RopeReader, TEditOps, TextEdit, TextNode,
	UserTerminal, ViewerOps, ViewerSpecs 
EXPORTS EditToolPrivate =

{ OPEN EditToolPrivate, ViewerSpecs;

sortButton: Buttons.Button;

BuildSortButtons: PUBLIC PROC = {
	startHeight: CARDINAL ← heightSoFar;
	initLeft: CARDINAL = entryLeft;
	BuildSortOrderEntry[];
	entryLeft ← (openRightWidth-scrollBarW-40)/2;
	heightSoFar ← startHeight;
	BuildSortKindEntry[];
	entryLeft ← initLeft;
	sortButton ← Buttons.Create["Sort!", DoSort,
		entryLeft, heightSoFar, 0, 0,
		NIL, TRUE, container, FALSE];
	sortButton.border ← FALSE;
	heightSoFar ← heightSoFar + entryHeight + entryVSpace };
	
----------------------------
----------------------------
sortOrderButton: Buttons.Button;
sortOrderLabel: Labels.Label;
sortIncreasing: PUBLIC BOOLEAN ← TRUE;
sortIncRope: Rope.Ref = "Sort increasing";
sortDecRope: Rope.Ref = "Sort decreasing";
sortIncAtom: ATOM = $EditToolSortIncreasing;
sortDecAtom: ATOM = $EditToolSortDecreasing;

SortOrderButton: Buttons.ButtonProc = {
	IF mainEditTool THEN ChangeState[sortOrderLabel,sortIncreasing,sortIncAtom,sortDecAtom]
	ELSE IF sortIncreasing THEN [] ← SortDecOp[] ELSE [] ← SortIncOp[] };

SortIncOp: TEditOps.CommandProc = {
	sortIncreasing ← TRUE;
	Labels.Set[sortOrderLabel,sortIncRope] };

SortDecOp: TEditOps.CommandProc = {
	sortIncreasing ← FALSE;
	Labels.Set[sortOrderLabel,sortDecRope] };

BuildSortOrderEntry: PROC = {
	[sortOrderLabel,sortOrderButton] ←
		BuildPair[SortOrderButton,sortIncreasing,sortIncRope,sortDecRope] };

----------------------------
----------------------------
sortKindButton: Buttons.Button;
sortKindLabel: Labels.Label;
sortKind: PUBLIC [0..2] ← sortText;
sortTextRope: Rope.Ref = "Sort Text (blanks delimit)";
sortLinesRope: Rope.Ref = "Sort Lines (CRs delimit)";
sortBranchesRope: Rope.Ref = "Sort Branches";

sortTextAtom: ATOM = $EditToolSortText;
sortLinesAtom: ATOM = $EditToolSortLines;
sortBranchesAtom: ATOM = $EditToolSortBranches;

SortKindButton: Buttons.ButtonProc = {
	IF mainEditTool THEN CycleTriple[sortKindLabel,sortKind,
		sortTextAtom, sortLinesAtom, sortBranchesAtom]
	ELSE SELECT sortKind FROM
		sortText => [] ← SortLinesOp[];
		sortLines => [] ← SortBranchesOp[];
		sortBranches => [] ← SortTextOp[];
		ENDCASE => ERROR };

SortTextOp: TEditOps.CommandProc = {
	sortKind ← sortText;
	Labels.Set[sortKindLabel,sortTextRope] };

SortLinesOp: TEditOps.CommandProc = {
	sortKind ← sortLines;
	Labels.Set[sortKindLabel,sortLinesRope] };

SortBranchesOp: TEditOps.CommandProc = {
	sortKind ← sortBranches;
	Labels.Set[sortKindLabel,sortBranchesRope] };

BuildSortKindEntry: PROC = {
	[sortKindLabel,sortKindButton] ←
		BuildTriple[SortKindButton,sortKind,
			sortTextRope, sortLinesRope, sortBranchesRope] };

----------------------------
----------------------------

doSortAtom: ATOM = $EditToolDoSort;

DoSort: Buttons.ButtonProc = { 
	IF mainEditTool THEN TEditOps.InterpretAtom[button,doSortAtom]
	ELSE [] ← DoSortOp[] };

DoSortOp: TEditOps.CommandProc = {
	sel: TEditDocument.Selection = TEditOps.GetSelData[];
	r1, r2: RopeReader.Ref;
	span: TextNode.Span;
	moved: BOOLEAN ← FALSE;
	
	increasing: BOOLEAN ← sortIncreasing;
	kind: [0..2] ← sortKind;
	
	GetRopeReaders: PROC = {
		r1 ← RopeReader.Create[]; r2 ← RopeReader.Create[] };
	
	event: UndoEvent.Ref ← TEditOps.CurrentEvent[];
	
	tSel↑ ← sel↑;
	
	IF tSel=NIL OR tSel.viewer=NIL OR tSel.viewer.class.flavor#$Text THEN {
		UserTerminal.BlinkDisplay[]; RETURN };
	
	span ← [tSel.start.pos, tSel.end.pos];

	IF kind=sortBranches THEN { 
	start, end, last, loc: TextNode.Ref;

	Next: PROC [n: TextNode.Ref] RETURNS [TextNode.Ref] = INLINE {
		RETURN [TextNode.Next[n]] };
		
	GoesBefore: PROC [a, b: TextNode.RefTextNode] RETURNS [BOOLEAN] = {
		-- returns true if node a goes before node b
		-- should look at children if a and b are equal *** FIX THIS ***
		compare: INTEGER ← RopeReader.Compare[r1: a.rope, r2: b.rope, rdr1:r1, rdr2:r2, case: FALSE];
		RETURN [IF increasing THEN compare < 0 ELSE compare > 0] };
	
	IsSibling: PROC [first, last: TextNode.Ref] RETURNS [BOOLEAN] = {
		FOR n: TextNode.Ref ← first, 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 { OPEN MessageWindow;
			Append["Selection for branch sort must end inside a sibling of start node.",TRUE];
			Blink[];
			RETURN };
		IF IsSibling[start,n] THEN { last ← n; EXIT };
		ENDLOOP;
	
	interrupt ← FALSE;	
	TEditOps.OpenRepeatSequence[];
	GetRopeReaders[];
	loc ← Next[start];
	end ← Next[last];
	UNTIL loc=end OR interrupt DO  -- insertion sort
		next: TextNode.Ref ← Next[loc];
		l: TextNode.Ref ← start;
		UNTIL l=loc DO -- find dest for [loc,next]
			nl: TextNode.Ref ← Next[l];
			txtLoc, txtL: TextNode.RefTextNode;
			IF (txtLoc ← TextNode.NarrowToTextNode[loc]) # NIL AND
				(txtL ← TextNode.NarrowToTextNode[l]) # NIL AND
				GoesBefore[txtLoc,txtL] THEN { -- move it
				IF loc=last THEN last ← TextNode.Previous[last];
				IF l=start THEN start ← loc;
				[] ← EditSpan.Move[dest: TextNode.MakeNodeLoc[l],
					source: TextNode.MakeNodeSpan[loc,TextNode.LastWithin[loc]],
					where: before, event: event];
				moved ← TRUE;
				EXIT };
			l ← nl;
			ENDLOOP;
		loc ← next;
		ENDLOOP;

	tSel.start.pos ← [start, 0];
	tSel.end.pos ← [last, MAX[TextEdit.Size[TextNode.NarrowToTextNode[last]],1]-1];
	SELECT tSel.granularity FROM
		node, branch => NULL;
		ENDCASE => tSel.granularity ← node;
	TEditOps.SetSelData[data: tSel, finalise: FALSE];
	}

	ELSE { -- sorting text/lines within a single node
	
	node: TextNode.RefTextNode;
	rope: Rope.ROPE;
	start, loc, end: TextNode.Offset;
	
	Next: PROC [at: TextNode.Offset] RETURNS [next: TextNode.Offset] = { -- scan to break
		RopeReader.SetPosition[r1,rope,at];
		next ← at;
		IF kind=sortText THEN { -- scan to blanks
			UNTIL next=end OR RopeEdit.BlankChar[RopeReader.Get[r1]] DO next ← next+1; ENDLOOP;
			IF next=end THEN RETURN; next ← next+1;
			UNTIL next=end OR ~RopeEdit.BlankChar[RopeReader.Get[r1]] DO next ← next+1; ENDLOOP }
		ELSE { -- scan to CRs
			UNTIL next=end OR RopeReader.Get[r1] = '\n DO next ← next+1; ENDLOOP;
			IF next=end THEN RETURN; next ← next+1;
			UNTIL next=end OR RopeReader.Get[r1] # '\n DO next ← next+1; ENDLOOP }};
	
	GoesBefore: PROC [a1, a2, b1, b2: TextNode.Offset] RETURNS [BOOLEAN] = {
		-- returns true if text [a1..a2] goes before [b1..b2]
		compare: INTEGER ← RopeReader.CompareSubstrs[r1:rope, r2:rope, rdr1:r1, rdr2:r2,
			start1: a1, start2: b1, len1: a2-a1, len2: b2-b1, case: FALSE];
		RETURN [IF increasing THEN compare < 0 ELSE compare > 0] };
	
	IF span.start.node # span.end.node THEN { OPEN MessageWindow;
		Append["Selection for text/line sort must be in a single node.",TRUE];
		Blink[]; RETURN };
	
	node ← TextNode.NarrowToTextNode[span.start.node];
	IF node=NIL THEN RETURN;
	
	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];
	IF end <= start THEN RETURN;
	
	end ← end+1;  -- location after the things to be sorted
	
	TEditOps.OpenRepeatSequence[];
	GetRopeReaders[];
	loc ← Next[start];
	UNTIL loc=end DO  -- insertion sort
		next: TextNode.Offset ← Next[loc];
		l: TextNode.Offset ← start;
		UNTIL l=loc DO -- find dest for [loc,next]
			nl: TextNode.Offset ← Next[l];
			IF GoesBefore[loc,next,l,nl] THEN { -- move it
				addedChar: BOOLEAN ← FALSE;
				IF next=end THEN { -- moving the last one
					lastChar: CHAR ← TextEdit.FetchChar[node,end-1];
					IF kind=sortText THEN {
						IF ~RopeEdit.BlankChar[lastChar] THEN { -- add blank
							[] ← TextEdit.InsertChar[dest: node, destLoc: end,
								char: '  , event: event];
							next ← end ← end+1; addedChar ← TRUE }}
					ELSE IF lastChar # '\n THEN { -- add CR
						[] ← TextEdit.InsertChar[dest: node, destLoc: end,
							char: '\n , event: event];
						next ← end ← end+1; addedChar ← TRUE }};
				[] ← TextEdit.MoveText[dest:node, destLoc:l,
					source:node, start:loc, len:next-loc, event:event];
				moved ← TRUE;
				IF addedChar THEN -- delete trailing delimiter
					TextEdit.DeleteText[text: node, start: end-1, len: 1, event: event];
				rope ← node.rope;
				EXIT };
			l ← nl;
			ENDLOOP;
		loc ← next;
		ENDLOOP;
	
	}; -- end of text/line case
	IF moved THEN ViewerOps.SetNewVersion[tSel.viewer];
	TEditOps.SetSelData[data: tSel, primary: TRUE, finalise: FALSE, setlooks: FALSE];
	TEditOps.PaintEdits[tSel]; -- repaint the selection
	};
----------------------------

RegisterSort: PUBLIC PROC = {
	TEditOps.Register[doSortAtom, DoSortOp];
	TEditOps.Register[sortIncAtom, SortIncOp];
	TEditOps.Register[sortDecAtom, SortDecOp];
	TEditOps.Register[sortTextAtom, SortTextOp];
	TEditOps.Register[sortLinesAtom, SortLinesOp];
	TEditOps.Register[sortBranchesAtom, SortBranchesOp];
	};

UnRegisterSort: PUBLIC PROC = {
	TEditOps.UnRegister[doSortAtom];
	TEditOps.UnRegister[sortIncAtom];
	TEditOps.UnRegister[sortDecAtom];
	TEditOps.UnRegister[sortTextAtom];
	TEditOps.UnRegister[sortLinesAtom];
	TEditOps.UnRegister[sortBranchesAtom];
	};

}.

..