unit OldSort;

{ Sort selected lines of text in Delphi's source code editor.
  Copyright © 1998 Tempest Software, Inc.
}

interface

// Sort the selection in the current file.
procedure SortSelection;

implementation

uses Classes, SysUtils, ExptIntf, EditIntf, ToolIntf;

resourcestring
  sCloseViews = 'Close all views but one';
  
type
  // Keep track of buffer positions for each line of text.
  // An edit writer's positions are relative to the original file.
  // When writing the sorted text, it is important to keep track
  // of the original position for each line.
  TPos = class
  private
    fLeft, fRight: LongInt;
  public
    constructor Create(Left, Right: LongInt);
    property Left: LongInt read fLeft;
    property Right: LongInt read fRight;
  end;
  TPosList = class(TList)
  public
    procedure AddPos(Left, Right: LongInt);
    destructor Destroy; override;
  end;

{ TPos }
constructor TPos.Create(Left, Right: Integer);
begin
  inherited Create;
  fLeft := Left;
  fRight := Right;
end;

{ TPosList }
procedure TPosList.AddPos(Left, Right: LongInt);
begin
  inherited Add(TPos.Create(Left, Right));
end;

destructor TPosList.Destroy;
var
  I: Integer;
  Obj: TObject;
begin
  for I := 0 to Count-1 do
  begin
    Obj := Self[I];
    Self[I] := nil;
    Obj.Free;
  end;
  inherited;
end;


// Sort the columnar selection by extracting the selected text
// in each line, sorting the text, and replacing the text in
// each line with the sorted text.
procedure SortColumns(Editor: TIEditorInterface; View: TIEditView);
var
  TopLeft: TEditPos;
  BottomRight: TEditPos;
  Left, Right: TEditPos;
  LeftChar, RightChar: TCharPos;
  LeftPos, RightPos: LongInt;
  Reader: TIEditReader;
  Writer: TIEditWriter;
  Text: string;
  Strings: TStringList;
  PosList: TPosList;
  Line: LongInt;
  I: Integer;
begin
  // Get the corners of the selected region.
  TopLeft     := TEditPos(Editor.BlockStart);
  BottomRight := TEditPos(Editor.BlockAfter);

  Strings := TStringList.Create;
  try
    PosList := TPosList.Create;
    try
      // Get the selected text.
      Reader := Editor.CreateReader;
      try
        Left.Col := TopLeft.Col;
        Right.Col := BottomRight.Col;
        for Line := TopLeft.Line to BottomRight.Line do
        begin
          // Convert the edit positions on the current line to
          // buffer positions. Delphi requires the intermediate
          // step of character positions.
          Left.Line := Line;
          Right.Line := Line;
          View.ConvertPos(True, Left,  LeftChar);
          View.ConvertPos(True, Right, RightChar);
          // include the character at RightChar
          Inc(RightChar.CharIndex);
          LeftPos  := View.CharPosToPos(LeftChar);
          RightPos := View.CharPosToPos(RightChar);
          SetLength(Text, RightPos - LeftPos);
          Reader.GetText(LeftPos, PChar(Text), Length(Text));
          // If the text includes the end of the line characters,
          // strip the CR and LF.
          while (Length(Text) > 0) and (Text[Length(Text)] in [#13, #10]) do
          begin
            Delete(Text, Length(Text), 1);
            Dec(RightPos);
          end;
          Strings.Add(Text);
          PosList.AddPos(LeftPos, RightPos);
       end;
      finally
        Reader.Free;
      end;

      // Now sort the text.
      Strings.Sort;

      // And write the sorted text, line by line.
      Writer := Editor.CreateUndoableWriter;
      try
        I := 0;
        for Line := TopLeft.Line to BottomRight.Line do
        begin
          with TPos(PosList[I]) do
          begin
            Writer.CopyTo(Left);
            Writer.DeleteTo(Right);
          end;
          Writer.Insert(PChar(Strings[I]));
          Inc(I);
        end;
      finally
        Writer.Free;
      end;
    finally
      PosList.Free;
    end;
  finally
    Strings.Free;
  end;

  // Set the cursor to the start of the sorted text.
  View.CursorPos := TopLeft;

  // Make sure the top of the sorted text is visible.
  // Scroll the edit window if ncessary.
  if (TopLeft.Line < View.TopPos.Line) or
     (BottomRight.Line >= View.TopPos.Line + View.ViewSize.CY)
  then
    View.TopPos := TopLeft;

  // Select the newly inserted, sorted text.
  Editor.BlockVisible := False;
  Editor.BlockType    := btColumn;
  Editor.BlockStart   := TCharPos(TopLeft);
  Editor.BlockAfter   := TCharPos(BottomRight);
  Editor.BlockVisible := True;
end;

// Sort the selection as lines.
procedure SortLines(Editor: TIEditorInterface; View: TIEditView);
var
  BlockStart: TCharPos;
  BlockAfter: TCharPos;
  StartPos, EndPos: LongInt;
  Reader: TIEditReader;
  Writer: TIEditWriter;
  TopPos, CursorPos: TEditPos;
  Text: string;
  Strings: TStringList;
begin
  // Get the limits of the selected text.
  BlockStart := Editor.BlockStart;
  BlockAfter := Editor.BlockAfter;

  // Sort entire lines, so modify the positions accordingly.
  BlockStart.CharIndex := 0;   // start of line
  if BlockAfter.CharIndex > 0 then
  begin
    // Select the entire line by setting the After position
    // to the start of the next line.
    BlockAfter.CharIndex := 0;
    Inc(BlockAfter.Line);
  end;

  // Convert the character positions to buffer positions.
  StartPos := View.CharPosToPos(BlockStart);
  EndPos   := View.CharPosToPos(BlockAfter);

  // Get the selected text.
  Reader := Editor.CreateReader;
  try
    SetLength(Text, EndPos - StartPos - 1);
    Reader.GetText(StartPos, PChar(Text), Length(Text));
  finally
    Reader.Free;
  end;

  // Sort the text. Use a TStringList because it's easy.
  Strings := TStringList.Create;
  try
    Strings.Text := Text;
    Strings.Sort;
    Text := Strings.Text;
  finally
    Strings.Free;
  end;

  // Replace the selection with the sorted text.
  Writer := Editor.CreateUndoableWriter;
  try
    Writer.CopyTo(StartPos);
    Writer.DeleteTo(EndPos);
    Writer.Insert(PChar(Text));
  finally
    Writer.Free;
  end;

  // Set the cursor to the start of the sorted text.
  View.ConvertPos(False, CursorPos, BlockStart);
  View.CursorPos := CursorPos;

  // Make sure the top of the sorted text is visible.
  // Scroll the edit window if ncessary.
  if (BlockStart.Line < View.TopPos.Line) or
     (BlockAfter.Line >= View.TopPos.Line + View.ViewSize.CY) then
  begin
    View.ConvertPos(False, TopPos, BlockStart);
    View.TopPos := TopPos;
  end;

  // Select the newly inserted, sorted text.
  Editor.BlockVisible := False;
  Editor.BlockType    := btNonInclusive;
  Editor.BlockStart   := BlockStart;
  Editor.BlockAfter   := BlockAfter;
  Editor.BlockVisible := True;
end;


// Sort the selection in the current file.
procedure SortSelection;
var
  Module: TIModuleInterface;
  Editor: TIEditorInterface;
  View: TIEditView;
begin
  // Get the module interface for the current file.
  with ToolServices do
    Module := GetModuleInterface(GetCurrentFile);
  // If no file is open, Module is nil.
  if Module = nil then
    Exit;

  try
    // Get the interface to the source editor.
    Editor := Module.GetEditorInterface;
    // If the file is not a source file, Editor is nil.
    if Editor = nil then
      Exit;

    try
      // The expert cannot tell which view is active, so force
      // the user to have only one view at a time.
      if Editor.GetViewCount > 1 then
        raise Exception.Create(sCloseViews);

      View := Editor.GetView(0);
      try
        // Columnar selections work entirely differently.
        if Editor.BlockType = btColumn then
          SortColumns(Editor, View)
        else
          SortLines(Editor, View);
      finally
        View.Free;
      end;
    finally
      Editor.Free;
    end;
    // Bring the focus back to the source editor window.
    Module.ShowSource;
  finally
    Module.Free;
  end;
end;

end.