unit uFunctionListDialog;
{$INCLUDE 'FarCtags.inc'}

interface

uses
  SysUtils, Windows, Classes, StrUtils,
  {$INCLUDE 'DelphiFarUnits.inc'} ,
  uCtags;

type
  TFunctionListDialog = class(TSearchDialog)
  private
    fCtags: TCtags;
    fContextEdits: array of TFarDialog.TEdit;
    fEditorID: TIntPtr;
    function GetContextEdit(Index: integer): TFarDialog.TEdit;
    function GetContextEditCount: integer;
  protected
    procedure CreateControls; override;
    procedure Clear; override;
    procedure CreateContextEdits;
    procedure SetSize(Width, Height: integer); override;
    function ValueMatches(Index: integer; const Search: string): boolean; override;
    procedure SelectedItemChanged(NewIndex: integer); override;
    const ContextSize = 3;
    const ContextWordWrap: boolean = True;
    const ContextWordWrapSimple: boolean = True;
    const MaxContextLineLength = 200;
    property ContextEdits[Index: integer]: TFarDialog.TEdit read GetContextEdit;
    property ContextEditCount: integer read GetContextEditCount;
    procedure PrepareDisplayValues;
  public
    constructor Create;
    destructor Destroy; override;
    function Execute: boolean;
    function ValueExists(const Search: string): boolean;
    property Ctags: TCtags read fCtags write fCtags;
    property EditorID: TIntPtr read fEditorID write fEditorID;
    property SearchDelay;
  end;

implementation

uses
  uMessages, uFarCtagsConsts;

{ TFunctionListDialog }

procedure TFunctionListDialog.Clear;
var
  i: integer;
begin
  inherited;
  for i := 0 to Pred(ContextEditCount) do begin
    fContextEdits[i] := nil;
  end;
end;

constructor TFunctionListDialog.Create;
begin
  inherited Create(DIALOG_FUNCTIONLIST_GUID);
  fEditorID := -1;
  Description := GetMsg(MFunctionListDescription);
end;

procedure TFunctionListDialog.CreateContextEdits;
var
  i: integer;
  LastControl: TControl;
begin
  for i := 0 to Pred(ContextEditCount) do begin
    fContextEdits[i].Free;
    fContextEdits[i] := nil;
  end;
  LastControl := ListValues;
  SetLength(fContextEdits, ContextSize);
  for i := 0 to Pred(ContextEditCount) do begin
    fContextEdits[i] := AddEdit(0, 0, 0, '');
    ContextEdits[i].ReadOnly := True;
    ContextEdits[i].EditorPart := True;
    ContextEdits[i].PlaceAfter(LastControl);
    LastControl := ContextEdits[i];
  end;
end;

procedure TFunctionListDialog.CreateControls;
begin
  inherited;
  CreateContextEdits;
end;

destructor TFunctionListDialog.Destroy;
begin
  inherited;
end;

function TFunctionListDialog.Execute: boolean;
begin
  Result := inherited Execute;
end;

function TFunctionListDialog.GetContextEdit(Index: integer): TFarDialog.TEdit;
begin
  if (Index < 0) or (Index >= Length(fContextEdits)) then
    raise EListError.Create('List index out of bounds');
  Result := fContextEdits[Index];
end;

function TFunctionListDialog.GetContextEditCount: integer;
begin
  Result := Length(fContextEdits);
end;

procedure TFunctionListDialog.PrepareDisplayValues;
var
  i, n: integer;
  Item: TCtagsItem;
  MaxWidth: integer;
  MaxLineNr: integer;
  LineNrWidth: integer;
  MaxNameLen: integer;
  MaxKindLen: integer;
  s: string;
begin
  MaxWidth := ListValues.Width - 2 {frame} - 2 {separators} ;
  MaxLineNr := 100;
  MaxNameLen := 0;
  MaxKindLen := 8;
  for i := 0 to Pred(Ctags.Count) do
  begin
    Item := Ctags[i];
    if Item.Line > MaxLineNr then
      MaxLineNr := Item.Line;
    n := Length(Item.Name);
    if n > MaxNameLen then
      MaxNameLen := n;
    n := Length(Item.Kind);
    if n > MaxKindLen then
      MaxKindLen := n;
  end;
  // Calculate width of the LineNr section
  LineNrWidth := 1;
  while MaxLineNr >= 10 do begin
    Inc(LineNrWidth);
    MaxLineNr := MaxLineNr div 10;
  end;
  // Calculate width of the Name and Kind sections
  if (LineNrWidth + 1 + MaxNameLen + 1 + MaxKindLen) > MaxWidth then
  begin
    if MaxKindLen > 10 then
      MaxKindLen := 10;
  end;
  MaxNameLen := MaxWidth - (LineNrWidth + 1 + 1 + MaxKindLen);
  // Generate the display values
  ListValues.BeginUpdate;
  try
    ListValues.Items.Clear;
    for i := 0 to Pred(Ctags.Count) do begin
      Item := Ctags[i];
      s := Format('%*.*s|%-*.*s|%-*.*s', [
          LineNrWidth, LineNrWidth, IntToStr(Item.Line)
        , MaxNameLen, MaxNameLen, Item.Name
        , MaxKindLen, MaxKindLen, Item.Kind
      ]);
      ListValues.Items.Add(s);
    end;
  finally
    ListValues.EndUpdate;
  end;
end;

procedure TFunctionListDialog.SetSize(Width, Height: integer);
var
  i: integer;
begin
  inherited;
  PrepareDisplayValues;
  ListValues.Height := ListValues.Height - ContextEditCount;
  for i := 0 to Pred(ContextEditCount) do begin
    ContextEdits[i].Left := ListValues.Left;
    ContextEdits[i].Top := ListValues.Top + ListValues.Height + i;
    ContextEdits[i].Width := ListValues.Width;
  end;
end;

procedure TFunctionListDialog.SelectedItemChanged(NewIndex: integer);
var
  i: integer;
  LineNr: integer;
  LineLen, MaxLineLen, MinLineLen, LineBreak: integer;
  Line: string;
begin
  inherited;
  if (ContextEditCount > 0) then begin
    if NewIndex >= 0 then begin
      BeginUpdate;
      try
        LineNr := Ctags[NewIndex].Line - 1;
        Line := '';
        LineLen := 0;
        MaxLineLen := ContextEdits[0].Width - 1;
        MinLineLen := MaxLineLen div 2;
        i := 0;
        while i < ContextEditCount do begin
          if ContextWordWrap and (Line <> '') then begin
            LineBreak := LineLen;
            if not ContextWordWrapSimple then begin
              while (LineBreak >= MinLineLen) and (CharInSet(Line[LineBreak], ['A'..'Z', 'a'..'z', '0'..'9', '_'])) do
                Dec(LineBreak);
              if LineBreak < MinLineLen then
                LineBreak := LineLen;
            end;
            ContextEdits[i].Text := Copy(Line, 1, LineBreak);
            ContextEdits[i].Unchanged := False;
            System.Delete(Line, 1, LineBreak);
            Inc(i);
          end
          else begin
            if TFarEditor.GetString(EditorID, LineNr, Line) then begin
              if ContextWordWrap then begin
                LineLen := Length(Line);
                if LineLen > MaxLineLen then
                  LineLen := MaxLineLen;
              end
              else begin
                ContextEdits[i].Text := Copy(Line, 1, MaxContextLineLength);
                ContextEdits[i].Unchanged := False;
                Inc(i);
              end;
            end
            else
              Break;
          end;
          Inc(LineNr);
        end;
      finally
        EndUpdate;
      end;
    end;
  end;
end;

function TFunctionListDialog.ValueExists(const Search: string): boolean;
var
  i: integer;
begin
  Result := False;
  for i := 0 to Pred(Ctags.Count) do begin
    if ValueMatches(i, Search) then begin
      Result := True;
      Break;
    end;
  end;
end;

function TFunctionListDialog.ValueMatches(Index: integer; const Search: string): boolean;
begin
  Result := (Search = '') or ContainsText(Ctags[Index].Name, Search);
end;

end.
