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.