unit
NewSort;
{ Sort selected lines of text in Delphi's source code editor.
This unit does exactly the same thing as SortSel.pas, but
it uses the new Tools API.
Copyright © 1998 Tempest Software, Inc.
}
interface
// Sort the selection in the current file.
procedure SortSelection;
implementation
uses Windows, Classes, SysUtils, ToolsAPI;
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(const Editor: IOTASourceEditor; const View: IOTAEditView);
var
TopLeft: TOTAEditPos;
BottomRight: TOTAEditPos;
Left, Right: TOTAEditPos;
LeftChar, RightChar: TOTACharPos;
LeftPos, RightPos: LongInt;
Reader: IOTAEditReader;
Writer: IOTAEditWriter;
Text: string;
Strings: TStringList;
PosList: TPosList;
Line: LongInt;
I: Integer;
begin
// Get the corners of the selected region.
TopLeft := TOTAEditPos(Editor.BlockStart);
BottomRight := TOTAEditPos(Editor.BlockAfter);
Strings := TStringList.Create;
try
PosList := TPosList.Create;
try
// Get the selected text.
Reader := Editor.CreateReader;
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;
Reader := nil;
// Now sort the text.
Strings.Sort;
// And write the sorted text, line by line.
Writer := Editor.CreateUndoableWriter;
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;
Writer := nil;
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 := TOTACharPos(TopLeft);
Editor.BlockAfter := TOTACharPos(BottomRight);
Editor.BlockVisible := True;
end;
// Sort the selection as lines.
procedure SortLines(const Editor: IOTASourceEditor; const View: IOTAEditView);
var
BlockStart: TOTACharPos;
BlockAfter: TOTACharPos;
StartPos, EndPos: LongInt;
Reader: IOTAEditReader;
Writer: IOTAEditWriter;
TopPos, CursorPos: TOTAEditPos;
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;
SetLength(Text, EndPos - StartPos - 1);
Reader.GetText(StartPos, PChar(Text), Length(Text));
Reader := nil;
// 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;
Writer.CopyTo(StartPos);
Writer.DeleteTo(EndPos);
Writer.Insert(PChar(Text));
Writer := nil;
// 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
ModuleServices: IOTAModuleServices;
Module: IOTAModule;
Intf: IOTAEditor;
Editor: IOTASourceEditor;
View: IOTAEditView;
I: Integer;
begin
ModuleServices := BorlandIDEServices as IOTAModuleServices;
// Get the module interface for the current file.
Module := ModuleServices.CurrentModule;
// If no file is open, Module is nil.
if Module = nil then
Exit;
// Get the interface to the source editor.
for I := 0 to Module.GetModuleFileCount-1 do
begin
Intf := Module.GetModuleFileEditor(I);
if Intf.QueryInterface(IOTASourceEditor, Editor) = S_OK then
Break;
end;
// If the file is not a source file, Editor is nil.
if Editor = nil then
Exit;
// The expert cannot tell which view is active, so force
// the user to have only one view at a time.
if Editor.EditViewCount > 1 then
raise Exception.Create(sCloseViews);
View := Editor.EditViews[0];
// Columnar selections work entirely differently.
if Editor.BlockType = btColumn then
SortColumns(Editor, View)
else
SortLines(Editor, View);
// Bring the focus back to the source editor window.
Editor.Show;
end;
end.