unit IdentSearch;

interface

uses
  Windows, SysUtils, Types, Classes, Forms, Controls, TextEditor, ASKernel,
  Graphics, Menus;

function ShowIdents(AOwner: TCustomForm; AKernel: TASKernel;
  ATypes: TASKernel.TIdentTypes; const AName: string): string;

implementation

uses
  Messages, StdCtrls, Character, Math, StrUtils, UxForm, ASDoc, DocView;

resourcestring
  SIdentifiers = 'Identifiers';

function _scale(X: Integer): Integer;
begin
  Result := MulDiv(X, Screen.PixelsPerInch, 96);
end;

type
  TIdentSearchForm = class(TUxForm)
  protected
    procedure WMWindowPosChanged(var Message: TWMWindowPosChanged);
      message WM_WINDOWPOSCHANGED;
  public
    Kernel: TASKernel;
    &Types: TASKernel.TIdentTypes;
    Filter, OldFilter: string;
    lb: TTextEditor;
    miVariables: TMenuItem;
    miFunctions: TMenuItem;
    miOperators: TMenuItem;
    pmList: TPopupMenu;
    procedure ListBoxSelect(Sender: TObject);
    procedure ListBoxKeyDown(Sender: TObject; var Key: Word;
      ShiftState: TShiftState);
    procedure ListBoxKeyPress(Sender: TObject; var Key: Char);
    procedure pmListPopup(Sender: TObject);
    procedure ToggleIdentType(Sender: TObject);
    procedure PopulateListBox;
  end;

function GetIdent(const AFullDescription: string): string;
var
  i: Integer;
begin
  for i := 1 to AFullDescription.Length do
    if AFullDescription[i].IsWhitespace then
      Exit(Copy(AFullDescription, 1, i - 1));
  Result := AFullDescription;
end;

function ShowIdents(AOwner: TCustomForm; AKernel: TASKernel;
  ATypes: TASKernel.TIdentTypes; const AName: string): string;
var
  IdSrc: TIdentSearchForm;
begin

  IdSrc := TIdentSearchForm.CreateNew(AOwner);
  try

    IdSrc.Kernel := AKernel;
    IdSrc.&Types := ATypes;
    IdSrc.Filter := AName;
    IdSrc.Caption := SIdentifiers;
    IdSrc.Color := clWhite;
    IdSrc.Width := _scale(500);
    IdSrc.Height := _scale(400);
    IdSrc.Constraints.MinWidth := _scale(200);
    IdSrc.Constraints.MinHeight := _scale(100);

    IdSrc.lb := TTextEditor.Create(IdSrc);
    IdSrc.lb.UseRuxThemes := True;
    IdSrc.lb.Parent := IdSrc.ParentWindow;
    IdSrc.lb.Align := alClient;
    IdSrc.lb.WantTab := False;
    IdSrc.lb.Font.Name := 'DejaVu Sans Mono';
    IdSrc.lb.Font.Size := 12;
    IdSrc.lb.ListBoxMode := True;
    IdSrc.lb.BorderType := btNone;
    IdSrc.lb.MarginLeft := _scale(8);
    IdSrc.PopulateListBox;
    IdSrc.lb.OnListBoxSelect := IdSrc.ListBoxSelect;
    IdSrc.lb.OnKeyDown := IdSrc.ListBoxKeyDown;
    IdSrc.lb.OnKeyPress := IdSrc.ListBoxKeyPress;
    IdSrc.lb.FormattingProcessor := TBracketListFormattingProcessor.Create(IdSrc.lb);

    IdSrc.pmList := TPopupMenu.Create(IdSrc);
    IdSrc.pmList.OnPopup := IdSrc.pmListPopup;

    IdSrc.miVariables := TMenuItem.Create(IdSrc.pmList);
    IdSrc.miVariables.Caption := 'Show variables';
    IdSrc.miVariables.Hint := 'Displays variables in the list.';
    IdSrc.miVariables.Tag := Ord(TASKernel.TIdentType.itVariable);
    IdSrc.miVariables.OnClick := IdSrc.ToggleIdentType;
    IdSrc.pmList.Items.Add(IdSrc.miVariables);

    IdSrc.miFunctions := TMenuItem.Create(IdSrc.pmList);
    IdSrc.miFunctions.Caption := 'Show functions';
    IdSrc.miFunctions.Hint := 'Displays kernel functions in the list.';
    IdSrc.miFunctions.Tag := Ord(TASKernel.TIdentType.itFunction);
    IdSrc.miFunctions.OnClick := IdSrc.ToggleIdentType;
    IdSrc.pmList.Items.Add(IdSrc.miFunctions);

    IdSrc.miOperators := TMenuItem.Create(IdSrc.pmList);
    IdSrc.miOperators.Caption := 'Show operators ';
    IdSrc.miOperators.Hint := 'Displays operators in the list.';
    IdSrc.miOperators.Tag := Ord(TASKernel.TIdentType.itOperator);
    IdSrc.miOperators.OnClick := IdSrc.ToggleIdentType;
    IdSrc.pmList.Items.Add(IdSrc.miOperators);

    IdSrc.lb.PopupMenu := IdSrc.pmList;

    if IdSrc.ShowModal = mrOk then
      Result := GetIdent(IdSrc.lb.Lines[IdSrc.lb.ListBoxItemIndex])
    else
      Result := '';

  finally
    IdSrc.Free;
  end;

end;

{ TIdentSearchForm }

procedure TIdentSearchForm.ListBoxKeyDown(Sender: TObject; var Key: Word;
  ShiftState: TShiftState);
begin
  case Key of
    VK_ESCAPE:
      ModalResult := mrCancel;
    VK_BACK:
      Key := 0;
    VK_F1:
      if lb.ListBoxItemIndex <> -1 then
        ShowDoc(GetIdent(lb.Lines[lb.ListBoxItemIndex]), (DocFrm = nil) or (DocFrm.FormFader = nil) or DocFrm.FormFader.Faded);
  end;
end;

procedure TIdentSearchForm.ListBoxKeyPress(Sender: TObject;
  var Key: Char);
begin
  if Key = Chr(VK_BACK) then
    Delete(Filter, Filter.Length, 1)
  else if not Key.IsControl then
    Filter := Filter + Key
  else
    Exit;
  Filter := Filter.Trim;
  if Filter <> OldFilter then
    PopulateListBox;
  Key := #0;
end;

procedure TIdentSearchForm.ListBoxSelect(Sender: TObject);
begin
  ModalResult := mrOk;
end;

procedure TIdentSearchForm.pmListPopup(Sender: TObject);
begin
  miVariables.Checked := TASKernel.TIdentType.itVariable in &Types;
  miFunctions.Checked := TASKernel.TIdentType.itFunction in &Types;
  miOperators.Checked := TASKernel.TIdentType.itOperator in &Types;
end;

procedure TIdentSearchForm.PopulateListBox;
var
  Idents: TArray<TASKernel.TIdentInfo>;
  i: Integer;
  Match: Boolean;
begin
  Match := False;
  Filter := Filter.Trim;
  if Filter.IsEmpty then
    Caption := SIdentifiers
  else
    Caption := SIdentifiers + ': ' + Filter;
  lb.BeginVisualUpdate;
  try
    lb.NewFile;
    lb.BeginAddLine;
    try
      Idents := Kernel.GetMatchingIdents(&Types, Filter);
      for i := 0 to High(Idents) do
        lb.AddLine(Idents[i].FullDescription);
    finally
      lb.EndAddLine;
    end;
    for i := 0 to lb.LineCount - 1 do
      if SameText(lb.Lines[i], Filter) then
      begin
        lb.CaretPos := Point(0, i);
        Match := True;
        Break;
      end;
    if not Match then
      for i := 0 to lb.LineCount - 1 do
        if lb.Lines[i].StartsWith(Filter, True) then
        begin
          lb.CaretPos := Point(0, i);
          Match := True;
          Break;
        end;
    if not Match then
      lb.TextFile.GotoSOF;
  finally
    lb.EndVisualUpdate(True);
  end;
  OldFilter := Filter;
  lb.ListBoxSelection := True;
end;

procedure TIdentSearchForm.ToggleIdentType(Sender: TObject);
begin
  if Sender is TComponent then
  begin
    case TASKernel.TIdentType(TComponent(Sender).Tag) of
      itVariable, itFcnVariable:
        if itVariable in &Types then
          &Types := &Types - [itVariable, itFcnVariable]
        else
          &Types := &Types + [itVariable, itFcnVariable];
      itFunction:
        if itFunction in &Types then
          &Types := &Types - [itFunction]
        else
          &Types := &Types + [itFunction];
      itOperator:
        if itOperator in &Types then
          &Types := &Types - [itOperator]
        else
          &Types := &Types + [itOperator];
    end;
    PopulateListBox;
  end;
end;

procedure TIdentSearchForm.WMWindowPosChanged(var Message: TWMWindowPosChanged);
begin
  inherited;
  if Assigned(lb) then
    lb.MoveBalloonPostScroll;
end;

end.
