Subversion Repositories fastphp

Rev

Rev 103 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. unit EditorMain;
  2.  
  3. {$Include 'FastPHP.inc'}
  4.  
  5. (*
  6.   This program requires
  7.   - Microsoft Internet Controls (TWebBrowser)
  8.     If you are using Delphi 10.1 Starter Edition, please import the ActiveX TLB
  9.     "Microsoft Internet Controls"
  10.   - SynEdit
  11.     You can obtain SynEdit via Embarcadero GetIt
  12. *)
  13.  
  14. // TODO: "crHourGlass" does not work if F9 is pressed!
  15. // TODO: if a scrapfile is already open, create a new scrap file (scrap2.php)
  16. // TODO: localize
  17. // TODO: wieso geht copy paste im twebbrowser nicht???
  18. // TODO: Wieso dauert webbrowser1 erste kompilierung so lange???
  19. // TODO: wieso kommt syntax fehler zweimal? einmal stderr einmal stdout?
  20. // TODO: Browser titlebar (link preview)
  21. // TODO: "jump to next/prev todo" buttons/shortcuts
  22. // TODO: "increase/decrease indent" buttons/shortcuts
  23. // TODO: Solve all compiler warnings, especially in regards Encoding
  24.  
  25. // Note in re Unicode:
  26. // - In Embarcadero® Delphi 10.4 Version 27.0.40680.4203:
  27. //   SynEdit correctly detects UTF-8 files without BOM as well as ANSI files with Umlauts.
  28. //   (Previous versions could not detect UTF-8 files without BOM?!)
  29. // - If BOM is existing, it will be removed. (which is good, because this is defined by PSR-1)
  30.  
  31. // Small things:
  32. // - The scroll bars of SynEdit are not affected by the dark theme
  33. // - dark theme full screen: doubleclick to desktop pixel "0,0" cannot be used to close the app
  34.  
  35. // Future ideas
  36. // - code insight
  37. // - verschiedene php versionen?
  38. // - webbrowser1 nur laden, wenn man den tab anwaehlt?
  39. // - doppelklick auf tab soll diesen schliessen
  40. // - Onlinehelp (www) aufrufen oder CHM datei
  41. // - Let all colors be adjustable
  42. // - code in bildschirmmitte (horizontal)?
  43. // - search in files of a directory
  44. // - Files in multiple tabs?
  45. // - Configurable tabulator display-width
  46.  
  47. interface
  48.  
  49. uses
  50.   // TODO: "{$IFDEF USE_SHDOCVW_TLB}_TLB{$ENDIF}" does not work with Delphi 10.2
  51.   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  52.   Dialogs, StdCtrls, OleCtrls, ComCtrls, ExtCtrls, ToolWin, IniFiles,
  53.   SynEditHighlighter, SynHighlighterPHP, SynEdit, ShDocVw, FindReplace,
  54.   ActnList, SynEditMiscClasses, SynEditSearch, RunPHP, ImgList, SynUnicode,
  55.   System.ImageList, System.Actions, Vcl.Menus, Vcl.Themes, System.UITypes,
  56.   SynEditCodeFolding;
  57.  
  58. {.$DEFINE OnlineHelp}
  59.  
  60. type
  61.   TForm1 = class(TForm)
  62.     PageControl1: TPageControl;
  63.     PlaintextTabSheet: TTabSheet;
  64.     HtmlTabSheet: TTabSheet;
  65.     Memo2: TMemo;
  66.     WebBrowser1: TWebBrowser;
  67.     Splitter1: TSplitter;
  68.     PageControl2: TPageControl;
  69.     CodeTabsheet: TTabSheet;
  70.     HelpTabsheet: TTabSheet;
  71.     WebBrowser2: TWebBrowser;
  72.     OpenDialog1: TOpenDialog;
  73.     Panel1: TPanel;
  74.     OpenDialog3: TOpenDialog;
  75.     SynEdit1: TSynEdit;
  76.     SynPHPSyn1: TSynPHPSyn;
  77.     Panel2: TPanel;
  78.     SynEditFocusTimer: TTimer;
  79.     Button1: TButton;
  80.     Button2: TButton;
  81.     Button3: TButton;
  82.     Button4: TButton;
  83.     Button5: TButton;
  84.     Button6: TButton;
  85.     ActionList: TActionList;
  86.     ActionFind: TAction;
  87.     ActionReplace: TAction;
  88.     ActionFindNext: TAction;
  89.     ActionGoto: TAction;
  90.     ActionSave: TAction;
  91.     ActionHelp: TAction;
  92.     ActionRun: TAction;
  93.     ActionESC: TAction;
  94.     Button7: TButton;
  95.     ActionOpen: TAction;
  96.     Button8: TButton;
  97.     Button9: TButton;
  98.     ActionFindPrev: TAction;
  99.     Timer1: TTimer;
  100.     ActionSpaceToTab: TAction;
  101.     Button11: TButton;
  102.     SynEditSearch1: TSynEditSearch;
  103.     TreeView1: TTreeView;
  104.     Splitter2: TSplitter;
  105.     btnLint: TButton;
  106.     ActionLint: TAction;
  107.     ImageList1: TImageList;
  108.     RunPopup: TPopupMenu;
  109.     OpeninIDE1: TMenuItem;
  110.     ActionRunConsole: TAction;
  111.     Runinconsole1: TMenuItem;
  112.     SavePopup: TPopupMenu;
  113.     Saveas1: TMenuItem;
  114.     Save1: TMenuItem;
  115.     SaveDialog1: TSaveDialog;
  116.     BtnSpecialChars: TImage;
  117.     BtnSpecialCharsOff: TImage;
  118.     BtnSpecialCharsOn: TImage;
  119.     BtnLightOn: TImage;
  120.     BtnLightOff: TImage;
  121.     BtnLight: TImage;
  122.     StartUpTimer: TTimer;
  123.     FileModTimer: TTimer;
  124.     GotoPHPdir1: TMenuItem;
  125.     PHPShell1: TMenuItem;
  126.     ActionSaveAs: TAction;
  127.     ActionGoToPHPDir: TAction;
  128.     ActionPHPInteractiveShell: TAction;
  129.     FontSizeTimer: TTimer;
  130.     procedure Run(Sender: TObject);
  131.     procedure RunConsole(Sender: TObject);
  132.     procedure FormShow(Sender: TObject);
  133.     procedure FormCreate(Sender: TObject);
  134.     procedure FormDestroy(Sender: TObject);
  135.     procedure FormClose(Sender: TObject; var Action: TCloseAction);
  136.     procedure PageControl2Changing(Sender: TObject; var AllowChange: Boolean);
  137.     procedure Memo2DblClick(Sender: TObject);
  138.     (*
  139.     {$IFDEF USE_SHDOCVW_TLB}
  140.     *)
  141.     procedure WebBrowser1BeforeNavigate2(ASender: TObject;
  142.       const pDisp: IDispatch; const URL, Flags, TargetFrameName, PostData,
  143.       Headers: OleVariant; var Cancel: WordBool);
  144.     (*
  145.     {$ELSE}
  146.     procedure WebBrowser1BeforeNavigate2(ASender: TObject;
  147.       const pDisp: IDispatch; var URL, Flags, TargetFrameName, PostData,
  148.       Headers: OleVariant; var Cancel: WordBool);
  149.     {$ENDIF}
  150.     *)
  151.     procedure BeforeNavigate(const URL: OleVariant; var Cancel: WordBool);
  152.     procedure SynEditFocusTimerTimer(Sender: TObject);
  153.     procedure ActionFindExecute(Sender: TObject);
  154.     procedure ActionReplaceExecute(Sender: TObject);
  155.     procedure ActionFindNextExecute(Sender: TObject);
  156.     procedure ActionGotoExecute(Sender: TObject);
  157.     procedure ActionSaveExecute(Sender: TObject);
  158.     procedure ActionHelpExecute(Sender: TObject);
  159.     procedure ActionRunExecute(Sender: TObject);
  160.     procedure ActionESCExecute(Sender: TObject);
  161.     procedure SynEdit1MouseWheelDown(Sender: TObject; Shift: TShiftState;
  162.       MousePos: TPoint; var Handled: Boolean);
  163.     procedure SynEdit1MouseWheelUp(Sender: TObject; Shift: TShiftState;
  164.       MousePos: TPoint; var Handled: Boolean);
  165.     procedure ActionOpenExecute(Sender: TObject);
  166.     procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
  167.     procedure Memo2KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  168.     procedure ActionFindPrevExecute(Sender: TObject);
  169.     procedure SynEdit1MouseCursor(Sender: TObject;
  170.       const aLineCharPos: TBufferCoord; var aCursor: TCursor);
  171.     procedure Timer1Timer(Sender: TObject);
  172.     procedure ActionSpaceToTabExecute(Sender: TObject);
  173.     procedure TreeView1DblClick(Sender: TObject);
  174.     procedure SynEdit1GutterClick(Sender: TObject; Button: TMouseButton; X, Y,
  175.       Line: Integer; Mark: TSynEditMark);
  176.     procedure SynEdit1PaintTransient(Sender: TObject; Canvas: TCanvas;
  177.       TransientType: TTransientType);
  178.     procedure ActionLintExecute(Sender: TObject);
  179.     procedure ActionRunConsoleExecute(Sender: TObject);
  180.     procedure BtnSpecialCharsClick(Sender: TObject);
  181.     procedure WebBrowser1WindowClosing(ASender: TObject;
  182.       IsChildWindow: WordBool; var Cancel: WordBool);
  183.     procedure BtnLightClick(Sender: TObject);
  184.     procedure StartUpTimerTimer(Sender: TObject);
  185.     procedure FileModTimerTimer(Sender: TObject);
  186.     procedure SynEdit1DropFiles(Sender: TObject; X, Y: Integer;
  187.       AFiles: TStrings);
  188.     procedure ActionSaveAsExecute(Sender: TObject);
  189.     procedure ActionGoToPHPDirExecute(Sender: TObject);
  190.     procedure ActionPHPInteractiveShellExecute(Sender: TObject);
  191.     procedure SynEdit1KeyDown(Sender: TObject; var Key: Word;
  192.       Shift: TShiftState);
  193.     procedure FontSizeTimerTimer(Sender: TObject);
  194.     procedure SynEdit1StatusChange(Sender: TObject; Changes: TSynStatusChanges);
  195.   private
  196.     hMutex: THandle;
  197.     CurSearchTerm: string;
  198.     HlpPrevPageIndex: integer;
  199.     SrcRep: TSynEditFindReplace;
  200.     {$IFDEF OnlineHelp}
  201.     gOnlineHelpWord: string;
  202.     {$ENDIF}
  203.     FileModLast: TDateTime;
  204.     FormShowRanOnce: boolean;
  205.     BrowserLoadedOnce: boolean;
  206.     procedure Help;
  207.     function InputRequestCallback(var data: AnsiString): boolean;
  208.     function OutputNotifyCallback(const data: AnsiString): boolean;
  209.     procedure RightTrimAll;
  210.   protected
  211.     ChmIndex: TMemIniFile;
  212.     FScrapFile: string;
  213.     FSaveAsFilename: string;
  214.     codeExplorer: TRunCodeExplorer;
  215.     procedure GotoLineNo(LineNo: integer);
  216.     function GetScrapFile: string;
  217.     procedure StartCodeExplorer;
  218.     procedure RefreshModifySign;
  219.     procedure Theme_Light;
  220.     procedure Theme_Dark;
  221.     function IsThemeDark: boolean;
  222.     function MarkUpLineReference(cont: string): string;
  223.     function ContainsUnicodeCharsInAnsiFile: boolean;
  224.     procedure SaveToFile(filename: string);
  225.   end;
  226.  
  227. var
  228.   Form1: TForm1;
  229.  
  230. implementation
  231.  
  232. {$R *.dfm}
  233.  
  234. {$R Cursors.res}
  235.  
  236. uses
  237.   Functions, StrUtils, WebBrowserUtils, FastPHPUtils, Math, ShellAPI, RichEdit,
  238.   FastPHPTreeView, ImageListEx, FastPHPConfig;
  239.  
  240. const
  241.   crMouseGutter = 1;
  242.  
  243. procedure TForm1.RefreshModifySign;
  244. var
  245.   tmp: string;
  246. begin
  247.   if SynEdit1 = nil then exit;
  248.  
  249.   tmp := Caption;
  250.  
  251.   tmp := StringReplace(tmp, '*', '', [rfReplaceAll]);
  252.   if SynEdit1.Modified then tmp := tmp + '*';
  253.  
  254.   if Caption <> tmp then Caption := tmp;
  255. end;
  256.  
  257. procedure TForm1.ActionFindNextExecute(Sender: TObject);
  258. begin
  259.   SrcRep.FindNext;
  260. end;
  261.  
  262. procedure TForm1.ActionFindPrevExecute(Sender: TObject);
  263. begin
  264.   SrcRep.FindPrev;
  265. end;
  266.  
  267. procedure TForm1.ActionGotoExecute(Sender: TObject);
  268. var
  269.   val: string;
  270.   lineno: integer;
  271. resourcestring
  272.   SGoTo = 'Go to';
  273.   SLineNumber = 'Line number:';
  274. begin
  275.   // TODO: VK_LMENU does not work! only works with AltGr but not Alt
  276.   // http://stackoverflow.com/questions/16828250/delphi-xe2-how-to-prevent-the-alt-key-stealing-focus ?
  277.  
  278.   if not InputQuery(SGoTo, SLineNumber, val) or
  279.      not TryStrToInt(val, lineno) then
  280.   begin
  281.     if SynEdit1.CanFocus then SynEdit1.SetFocus;
  282.     exit;
  283.   end;
  284.   GotoLineNo(lineno);
  285. end;
  286.  
  287. procedure TForm1.ActionGoToPHPDirExecute(Sender: TObject);
  288. var
  289.   phpExe: string;
  290. begin
  291.   phpExe := GetPHPExe;
  292.   if phpExe <> '' then
  293.     ShellExecute(Handle, 'open', 'explorer.exe', PChar(ExtractFilePath(phpExe)), '', SW_NORMAL);
  294. end;
  295.  
  296. procedure TForm1.ActionHelpExecute(Sender: TObject);
  297. begin
  298.   Help;
  299.   if PageControl2.ActivePage = HelpTabsheet then
  300.     WebBrowser2.SetFocus
  301.   else if PageControl2.ActivePage = CodeTabsheet then
  302.     SynEdit1.SetFocus;
  303. end;
  304.  
  305. procedure TForm1.ActionLintExecute(Sender: TObject);
  306. begin
  307.   Run(Sender);
  308.   SynEdit1.SetFocus;
  309. end;
  310.  
  311. procedure TForm1.ActionOpenExecute(Sender: TObject);
  312. begin
  313.   If OpenDialog3.Execute then
  314.   begin
  315.     ShellExecute(0, 'open', PChar(ParamStr(0)), PChar('"' + OpenDialog3.FileName + '"'), '', SW_NORMAL);
  316.   end;
  317. end;
  318.  
  319. procedure TForm1.ActionPHPInteractiveShellExecute(Sender: TObject);
  320. var
  321.   phpExe: string;
  322. begin
  323.   phpExe := GetPHPExe;
  324.   if phpExe <> '' then
  325.     ShellExecute(Handle, 'open', PChar(phpExe), '-a', PChar(ExtractFilePath(phpExe)), SW_NORMAL);
  326. end;
  327.  
  328. procedure TForm1.ActionReplaceExecute(Sender: TObject);
  329. begin
  330.   SrcRep.ReplaceExecute;
  331. end;
  332.  
  333. procedure TForm1.ActionRunConsoleExecute(Sender: TObject);
  334. begin
  335.   RunConsole(Sender);
  336.   SynEdit1.SetFocus;
  337. end;
  338.  
  339. procedure TForm1.ActionRunExecute(Sender: TObject);
  340. begin
  341.   Run(Sender);
  342.   SynEdit1.SetFocus;
  343. end;
  344.  
  345. procedure TForm1.RightTrimAll;
  346. var
  347.   i: integer;
  348. begin
  349.   for i := 0 to SynEdit1.Lines.Count-1 do
  350.   begin
  351.     SynEdit1.Lines.Strings[i] := TrimRight(SynEdit1.Lines.Strings[i]);
  352.   end;
  353.  
  354.   (*
  355.   while (SynEdit1.Lines.Count > 0) and (SynEdit1.Lines.Strings[SynEdit1.Lines.Count-1] = '') do
  356.   begin
  357.     SynEdit1.Lines.Delete(SynEdit1.Lines.Count-1);
  358.   end;
  359.   if SynEdit1.SelStart > Length(SynEdit1.Text)-1 then
  360.   begin
  361.     // TODO: This code does not work...
  362.     SynEdit1.SelStart := Length(SynEdit1.Text)-1;
  363.     SynEdit1.SelEnd   := Length(SynEdit1.Text)-1;
  364.   end;
  365.   *)
  366. end;
  367.  
  368. procedure TForm1.ActionSaveAsExecute(Sender: TObject);
  369. var
  370.   hMutexNew: THandle;
  371. resourcestring
  372.   SCannotSaveBecauseMutex = 'Cannot save because file "%s", because it is alrady open in another FastPHP window!';
  373. begin
  374.   if SaveDialog1.Execute then
  375.   begin
  376.     {$REGION 'Switch mutex'}
  377.     hMutexNew := CreateMutex(nil, True, PChar('FastPHP'+md5(UpperCase(SaveDialog1.FileName))));
  378.     if GetLastError = ERROR_ALREADY_EXISTS then
  379.     begin
  380.       ShowMessageFmt(SCannotSaveBecauseMutex, [SaveDialog1.FileName]);
  381.       Close;
  382.     end;
  383.  
  384.     if hMutex <> 0 then CloseHandle(hMutex); // Note: ReleaseMutex does not work as expected!
  385.     hMutex := hMutexNew;
  386.     {$ENDREGION}
  387.  
  388.     FSaveAsFilename := SaveDialog1.FileName;
  389.     Caption := Copy(Caption, 1, Pos(' - ', Caption)-1) + ' - ' + FSaveAsFilename;
  390.     Application.Title := Format('%s - FastPHP', [ExtractFileName(FSaveAsFilename)]); // do not translate!
  391.     Button7.Click;
  392.   end;
  393. end;
  394.  
  395. procedure TForm1.ActionSaveExecute(Sender: TObject);
  396. begin
  397.   RightTrimAll;
  398.   SaveToFile(GetScrapFile);
  399.   SynEdit1.Modified := false;
  400.   RefreshModifySign;
  401.   if SynEdit1.CanFocus then SynEdit1.SetFocus;
  402. end;
  403.  
  404. procedure TForm1.ActionSpaceToTabExecute(Sender: TObject);
  405.  
  406.     function SpacesAtBeginning(line: string): integer;
  407.     begin
  408.       result := 0;
  409.       if Trim(line) = '' then exit;
  410.       while line[result+1] = ' ' do
  411.       begin
  412.         inc(result);
  413.       end;
  414.     end;
  415.  
  416.     function GuessIndent(lines: {$IFDEF UNICODE}TStrings{$ELSE}TUnicodeStrings{$ENDIF}): integer;
  417.       function _Check(indent: integer): boolean;
  418.       var
  419.         i: integer;
  420.       begin
  421.         result := true;
  422.         for i := 0 to lines.Count-1 do
  423.           if SpacesAtBeginning(lines.Strings[i]) mod indent <> 0 then
  424.           begin
  425.             // ShowMessageFmt('Zeile "%s" nicht durch %d teilbar!', [lines.strings[i], indent]);
  426.             result := false;
  427.             exit;
  428.           end;
  429.       end;
  430.     var
  431.       i: integer;
  432.     begin
  433.       for i := 8 downto 2 do
  434.       begin
  435.         if _Check(i) then
  436.         begin
  437.           result := i;
  438.           exit;
  439.         end;
  440.       end;
  441.       result := -1;
  442.     end;
  443.  
  444.     procedure SpaceToTab(lines: {$IFDEF UNICODE}TStrings{$ELSE}TUnicodeStrings{$ENDIF}; indent: integer);
  445.     var
  446.       i, spaces: integer;
  447.       newval: string;
  448.       somethingchanged: boolean;
  449.     begin
  450.       somethingchanged := false;
  451.       for i := 0 to lines.Count-1 do
  452.       begin
  453.         spaces := SpacesAtBeginning(lines.Strings[i]);
  454.         newval := StringOfChar(#9, spaces div indent) + StringOfChar(' ', spaces mod indent) + Copy(lines.Strings[i], spaces+1, Length(lines.Strings[i])-spaces);
  455.         if lines.Strings[i] <> newval then
  456.         begin
  457.           somethingchanged := true;
  458.           lines.Strings[i] := newval;
  459.         end;
  460.       end;
  461.       if somethingchanged then
  462.       begin
  463.         RefreshModifySign;
  464.       end;
  465.     end;
  466.  
  467.     function SpacesAvailable(lines: {$IFDEF UNICODE}TStrings{$ELSE}TUnicodeStrings{$ENDIF}): boolean;
  468.     var
  469.       i, spaces: integer;
  470.     begin
  471.       for i := 0 to lines.Count-1 do
  472.       begin
  473.         spaces := SpacesAtBeginning(lines.Strings[i]);
  474.         if spaces > 0 then
  475.         begin
  476.           result := true;
  477.           exit;
  478.         end;
  479.       end;
  480.       result := false;
  481.       exit;
  482.     end;
  483.  
  484. var
  485.   val: string;
  486.   ind: integer;
  487. resourcestring
  488.   SNoLinesAvailable = 'No lines with spaces at the beginning available';
  489.   SSpacesToLabs = 'Spaces to tabs';
  490.   SIndent = 'Indent:';
  491. begin
  492.   // TODO: if something is selected, only process the selected part
  493.  
  494.   if not SpacesAvailable(SynEdit1.Lines) then
  495.   begin
  496.     MessageDlg(SNoLinesAvailable, mtInformation, [mbOk], 0);
  497.     exit;
  498.   end;
  499.  
  500.   ind := GuessIndent(SynEdit1.Lines);
  501.   if ind <> -1 then val := IntToStr(ind);
  502.  
  503.   if not InputQuery(SSpacesToLabs, SIndent, val) or
  504.      not TryStrToInt(Trim(val), ind) then
  505.   begin
  506.     if SynEdit1.CanFocus then SynEdit1.SetFocus;
  507.     exit;
  508.   end;
  509.  
  510.   if ind = 0 then exit;
  511.   SpaceToTab(SynEdit1.Lines, ind);
  512. end;
  513.  
  514. procedure TForm1.ActionESCExecute(Sender: TObject);
  515. begin
  516.   if (HlpPrevPageIndex <> -1) and (PageControl2.ActivePage = HelpTabSheet) and
  517.      (HelpTabsheet.TabVisible) then
  518.   begin
  519.     PageControl2.ActivePageIndex := HlpPrevPageIndex;
  520.     HelpTabsheet.TabVisible := false;
  521.   end;
  522.  
  523.   // Dirty hack...
  524.   SrcRep.CloseDialogs;
  525. end;
  526.  
  527. procedure TForm1.ActionFindExecute(Sender: TObject);
  528. begin
  529.   SrcRep.FindExecute;
  530. end;
  531.  
  532. procedure TForm1.Run(Sender: TObject);
  533. var
  534.   bakTS: TTabSheet;
  535.   //ss: TStringStream;
  536.   //bakPos: Int64;
  537. begin
  538.   memo2.Lines.Text := '';
  539.  
  540.   if not BrowserLoadedOnce then
  541.   begin
  542.     bakTS := PageControl1.ActivePage;
  543.     try
  544.       PageControl1.ActivePage := HtmlTabSheet; // Required for the first time, otherwise, WebBrowser1.Clear will hang
  545.       Webbrowser1.Clear;
  546.     finally
  547.       PageControl1.ActivePage := bakTS;
  548.     end;
  549.     BrowserLoadedOnce := true;
  550.   end
  551.   else
  552.     Webbrowser1.Clear;
  553.  
  554.   Screen.Cursor := crHourGlass; // TODO: Doesn't work with F9
  555.   Application.ProcessMessages;
  556.  
  557.   try
  558.     ActionSave.Execute; // TODO: if it is not the scrap file: do not save the file, since the user did not intended to save... better create a temporary file and run it instead.
  559.  
  560.     // TODO 70421 * <fastphp> implement flush() with ContentCallBack implementieren... For long running scripts I want to see status changes via javascript which are loaded step by step
  561.     // TODO 70422 * <fastphp> when a script has an endless loop, i want to have a possibility to cancel it
  562.     if SynEdit1.Lines.Encoding = TEncoding.UTF8 then
  563.       memo2.Lines.Text := Utf8Decode(RunPHPScript(GetScrapFile, Sender=ActionLint, False)) // if we have a UTF-8 file, then the DOS output is double-UTF8 encoded
  564.     else
  565.       memo2.Lines.Text := RunPHPScript(GetScrapFile, Sender=ActionLint, False);
  566.  
  567.     {$REGION 'Show in Web Browser'}
  568.     if SynEdit1.Lines.Encoding = TEncoding.UTF8 then
  569.       Webbrowser1.LoadHTML(MarkUpLineReference('<meta charset="utf-8">'+memo2.Lines.Text), GetScrapFile)
  570.     else
  571.       Webbrowser1.LoadHTML(MarkUpLineReference(memo2.Lines.Text), GetScrapFile);
  572.  
  573.     // Alternatively:
  574.     (*
  575.     ss := TstringStream.Create;
  576.     ss.WriteString(MarkUpLineReference(memo2.Lines.Text));
  577.     ss.Position := 0;
  578.     Webbrowser1.LoadStream(ss, GetScrapFile);
  579.     Webbrowser1.Wait;
  580.     ss.Free;
  581.     *)
  582.     {$ENDREGION}
  583.  
  584.     if IsTextHTML(memo2.lines.text) then
  585.       PageControl1.ActivePage := HtmlTabSheet
  586.     else
  587.       PageControl1.ActivePage := PlaintextTabSheet;
  588.   finally
  589.     Screen.Cursor := crDefault;
  590.   end;
  591. end;
  592.  
  593. procedure TForm1.RunConsole(Sender: TObject);
  594. begin
  595.   ActionSave.Execute; // TODO: if it is not the scrap file: do not save the file, since the user did not intended to save... better create a temporary file and run it instead.
  596.   RunPHPScript(GetScrapFile, Sender=ActionLint, True);
  597. end;
  598.  
  599. procedure TForm1.SynEdit1DropFiles(Sender: TObject; X, Y: Integer;
  600.   AFiles: TStrings);
  601. var
  602.   FileName: string;
  603. const
  604.   WARN_FILE_COUNT = 10;
  605. resourcestring
  606.   SAreYouSure = 'Are you sure you want to open %d files?';
  607. begin
  608.   if AFiles.Count > WARN_FILE_COUNT then
  609.   begin
  610.     if not MessageDlg(Format(SAreYouSure, [WARN_FILE_COUNT]), mtConfirmation, mbYesNoCancel, 0) <> mrYes then
  611.       exit;
  612.   end;
  613.  
  614.   for FileName in AFiles do
  615.   begin
  616.     ShellExecute(0, 'open', PChar(ParamStr(0)), PChar('"' + FileName + '"'), '', SW_NORMAL);
  617.   end;
  618. end;
  619.  
  620. procedure TForm1.SynEdit1GutterClick(Sender: TObject; Button: TMouseButton; X,
  621.   Y, Line: Integer; Mark: TSynEditMark);
  622. begin
  623.   (*
  624.   TSynEdit(Sender).CaretX := 1;
  625.   TSynEdit(Sender).CaretY := Line;
  626.   TSynEdit(Sender).SelLength := Length(TSynEdit(Sender).LineText);
  627.   *)
  628. end;
  629.  
  630. procedure TForm1.SynEdit1KeyDown(Sender: TObject; var Key: Word;
  631.   Shift: TShiftState);
  632. begin
  633.   if (Shift = [ssCtrl, ssShift]) and (Key = ord('C')) then
  634.   begin
  635.     // Disable "Column Select Mode" (https://github.com/SynEdit/SynEdit/blob/master/Source/SynEditKeyCmds.pas#L879)
  636.     // which can be enabled by pressing Ctrl+Shift+C
  637.     // Reasons why we disable it:
  638.     // 1. I think nobody needs this, and sometimes you accidentally press it
  639.     // 2. Ctrl+Shift+L would the combination to disable column select mode,
  640.     //    but you cannot use it, because it is already in use for linting
  641.     Key := 0;
  642.   end;
  643. end;
  644.  
  645. procedure TForm1.SynEdit1MouseCursor(Sender: TObject; const aLineCharPos: TBufferCoord; var aCursor: TCursor);
  646. {$IFDEF OnlineHelp}
  647. var
  648.   Line: Integer;
  649.   Column: Integer;
  650.   word: string;
  651. begin
  652.   Line  := aLineCharPos.Line-1;
  653.   Column := aLineCharPos.Char-1;
  654.   word := GetWordUnderPos(TSynEdit(Sender), Line, Column);
  655.   if word <> gOnlineHelpWord then
  656.   begin
  657.     gOnlineHelpWord := word;
  658.     Timer1.Enabled := false;
  659.     Timer1.Enabled := true;
  660.   end;
  661. {$ELSE}
  662. begin
  663. {$ENDIF}
  664. end;
  665.  
  666. procedure TForm1.SynEdit1MouseWheelDown(Sender: TObject; Shift: TShiftState;
  667.   MousePos: TPoint; var Handled: Boolean);
  668. begin
  669.   if ssCtrl in Shift then
  670.   begin
  671.     SynEdit1.Font.Size := Max(SynEdit1.Font.Size - 1, 5);
  672.     Handled := true;
  673.   end
  674.   else Handled := false;
  675. end;
  676.  
  677. procedure TForm1.SynEdit1MouseWheelUp(Sender: TObject; Shift: TShiftState;
  678.   MousePos: TPoint; var Handled: Boolean);
  679. begin
  680.   if ssCtrl in Shift then
  681.   begin
  682.     SynEdit1.Font.Size := SynEdit1.Font.Size + 1;
  683.     Handled := true;
  684.   end
  685.   else Handled := false;
  686. end;
  687.  
  688. procedure TForm1.SynEdit1PaintTransient(Sender: TObject; Canvas: TCanvas; TransientType: TTransientType);
  689. var
  690.   Editor: TSynEdit;
  691.   OpenChars: array of WideChar;//[0..2] of WideChar=();
  692.   CloseChars: array of WideChar;//[0..2] of WideChar=();
  693.  
  694.   function IsCharBracket(AChar: WideChar): Boolean;
  695.   begin
  696.     case AChar of
  697.       '{','[','(','<','}',']',')','>':
  698.         Result := True;
  699.       else
  700.         Result := False;
  701.     end;
  702.   end;
  703.  
  704.   function CharToPixels(P: TBufferCoord): TPoint;
  705.   begin
  706.     Result := Editor.RowColumnToPixels(Editor.BufferToDisplayPos(P));
  707.   end;
  708.  
  709. var
  710.   COLOR_FG: TColor;
  711.   COLOR_BG: TColor;
  712.   P: TBufferCoord;
  713.   Pix: TPoint;
  714.   D: TDisplayCoord;
  715.   S: UnicodeString;
  716.   I: Integer;
  717.   Attri: TSynHighlighterAttributes;
  718.   ArrayLength: Integer;
  719.   start: Integer;
  720.   TmpCharA, TmpCharB: WideChar;
  721. begin
  722.   // Source: https://github.com/SynEdit/SynEdit/blob/master/Demos/OnPaintTransientDemo/Unit1.pas
  723.  
  724.   if IsThemeDark then
  725.   begin
  726.     COLOR_FG := clLime;
  727.     COLOR_BG := clGreen;
  728.   end
  729.   else
  730.   begin
  731.     COLOR_FG := clGreen;
  732.     COLOR_BG := clLime;
  733.   end;
  734.  
  735.   if TSynEdit(Sender).SelAvail then exit;
  736.   Editor := TSynEdit(Sender);
  737.   ArrayLength:= 3;
  738.  
  739.   (*
  740.   if (Editor.Highlighter = shHTML) or (Editor.Highlighter = shXML) then
  741.     inc(ArrayLength);
  742.   *)
  743.  
  744.   SetLength(OpenChars, ArrayLength);
  745.   SetLength(CloseChars, ArrayLength);
  746.   for i := 0 to ArrayLength - 1 do
  747.   begin
  748.     case i of
  749.       0: begin OpenChars[i] := '('; CloseChars[i] := ')'; end;
  750.       1: begin OpenChars[i] := '{'; CloseChars[i] := '}'; end;
  751.       2: begin OpenChars[i] := '['; CloseChars[i] := ']'; end;
  752.       3: begin OpenChars[i] := '<'; CloseChars[i] := '>'; end;
  753.     end;
  754.   end;
  755.  
  756.   P := Editor.CaretXY;
  757.   D := Editor.DisplayXY;
  758.  
  759.   Start := Editor.SelStart;
  760.  
  761.   if (Start > 0) and (Start <= length(Editor.Text)) then
  762.     TmpCharA := Editor.Text[Start]
  763.   else
  764.     TmpCharA := #0;
  765.  
  766.   if (Start > 0){Added by VTS} and (Start < length(Editor.Text)) then
  767.     TmpCharB := Editor.Text[Start + 1]
  768.   else
  769.     TmpCharB := #0;
  770.  
  771.   if not IsCharBracket(TmpCharA) and not IsCharBracket(TmpCharB) then exit;
  772.   S := TmpCharB;
  773.   if not IsCharBracket(TmpCharB) then
  774.   begin
  775.     P.Char := P.Char - 1;
  776.     S := TmpCharA;
  777.   end;
  778.   Editor.GetHighlighterAttriAtRowCol(P, S, Attri);
  779.  
  780.   if (Editor.Highlighter.SymbolAttribute = Attri) then
  781.   begin
  782.     for i := low(OpenChars) to High(OpenChars) do
  783.     begin
  784.       if (S = OpenChars[i]) or (S = CloseChars[i]) then
  785.       begin
  786.         Pix := CharToPixels(P);
  787.  
  788.         Editor.Canvas.Brush.Style := bsSolid;//Clear;
  789.         Editor.Canvas.Font.Assign(Editor.Font);
  790.         Editor.Canvas.Font.Style := Attri.Style;
  791.  
  792.         if (TransientType = ttAfter) then
  793.         begin
  794.           Editor.Canvas.Font.Color := COLOR_FG;
  795.           Editor.Canvas.Brush.Color := COLOR_BG;
  796.         end
  797.         else
  798.         begin
  799.           Editor.Canvas.Font.Color := Attri.Foreground;
  800.           Editor.Canvas.Brush.Color := Attri.Background;
  801.         end;
  802.         if Editor.Canvas.Font.Color = clNone then
  803.           Editor.Canvas.Font.Color := Editor.Font.Color;
  804.         if Editor.Canvas.Brush.Color = clNone then
  805.           Editor.Canvas.Brush.Color := Editor.Color;
  806.  
  807.         Editor.Canvas.TextOut(Pix.X, Pix.Y, S);
  808.         P := Editor.GetMatchingBracketEx(P);
  809.  
  810.         if (P.Char > 0) and (P.Line > 0) then
  811.         begin
  812.           Pix := CharToPixels(P);
  813.           if Pix.X > Editor.Gutter.Width then
  814.           begin
  815.             {$REGION 'Added by ViaThinkSoft'}
  816.             if (TransientType = ttAfter) then
  817.             begin
  818.               Editor.Canvas.Font.Color := COLOR_FG;
  819.               Editor.Canvas.Brush.Color := COLOR_BG;
  820.             end
  821.             else
  822.             begin
  823.               Editor.Canvas.Font.Color := Attri.Foreground;
  824.               Editor.Canvas.Brush.Color := Attri.Background;
  825.             end;
  826.             if Editor.Canvas.Font.Color = clNone then
  827.               Editor.Canvas.Font.Color := Editor.Font.Color;
  828.             if Editor.Canvas.Brush.Color = clNone then
  829.               Editor.Canvas.Brush.Color := Editor.Color;
  830.             {$ENDREGION}
  831.             if S = OpenChars[i] then
  832.               Editor.Canvas.TextOut(Pix.X, Pix.Y, CloseChars[i])
  833.             else Editor.Canvas.TextOut(Pix.X, Pix.Y, OpenChars[i]);
  834.           end;
  835.         end;
  836.       end;
  837.     end;
  838.     Editor.Canvas.Brush.Style := bsSolid;
  839.   end;
  840. end;
  841.  
  842. procedure TForm1.SynEdit1StatusChange(Sender: TObject;
  843.   Changes: TSynStatusChanges);
  844. begin
  845.   if scModified in Changes then
  846.     RefreshModifySign;
  847. end;
  848.  
  849. procedure TForm1.SynEditFocusTimerTimer(Sender: TObject);
  850. begin
  851.   SynEditFocusTimer.Enabled := false;
  852.   Button1.SetFocus; // Workaround for weird bug... This (and the timer) is necessary to get the focus to SynEdit1
  853.   SynEdit1.SetFocus;
  854. end;
  855.  
  856. procedure TForm1.Theme_Dark;
  857. begin
  858.   if IsThemeDark then exit;
  859.   TStyleManager.TrySetStyle('Windows10 SlateGray'); // do not translate
  860.   Color := 1316887;
  861.   Font.Color := clCream;
  862.   //Memo2.Font.Color := clCream;
  863.   //Memo2.ParentColor := true;
  864.   SynEdit1.ActiveLineColor := 2238502;
  865.   SynEdit1.Color := 1316887;
  866.   SynEdit1.Font.Color := clCream;
  867.   SynEdit1.Gutter.Color := 1316887;
  868.   SynEdit1.Gutter.Font.Color := clCream;
  869.   SynEdit1.Gutter.GradientStartColor := 2238502;
  870.   SynEdit1.Gutter.GradientEndColor := 1316887;
  871.   SynPHPSyn1.CommentAttri.Foreground := $00837B82;
  872.   SynPHPSyn1.IdentifierAttri.Foreground := 9627120;
  873.   SynPHPSyn1.KeyAttri.Foreground := 4157595;
  874.   SynPHPSyn1.NumberAttri.Foreground := 5008079;
  875.   SynPHPSyn1.StringAttri.Foreground := 6987151;
  876.   SynPHPSyn1.SymbolAttri.Foreground := 8769754;
  877.   SynPHPSyn1.VariableAttri.Foreground := 6924493;
  878. end;
  879.  
  880. procedure TForm1.Theme_Light;
  881. begin
  882.   if not IsThemeDark then exit;
  883.   TStyleManager.TrySetStyle('Windows'); // do not translate
  884.   Color := clBtnFace;
  885.   Font.Color := clWindowText;
  886.   //Memo2.Font.Color := clWindowText;
  887.   SynEdit1.ActiveLineColor := 14680010;
  888.   SynEdit1.Color := clCream;
  889.   SynEdit1.Font.Color := clWindowText;
  890.   SynEdit1.Gutter.Color := clBtnFace;
  891.   SynEdit1.Gutter.Font.Color := clWindowText;
  892.   SynEdit1.Gutter.GradientStartcolor := cl3dLight;
  893.   SynEdit1.Gutter.GradientEndColor := clBtnFace;;
  894.   SynPHPSyn1.CommentAttri.Foreground := 33023;
  895.   SynPHPSyn1.IdentifierAttri.Foreground := 4194304;
  896.   SynPHPSyn1.KeyAttri.Foreground := 4227072;
  897.   SynPHPSyn1.NumberAttri.Foreground := 213;
  898.   SynPHPSyn1.StringAttri.Foreground := 13762560;
  899.   SynPHPSyn1.SymbolAttri.Foreground := 4227072;
  900.   SynPHPSyn1.VariableAttri.Foreground := 213;
  901. end;
  902.  
  903. procedure TForm1.Timer1Timer(Sender: TObject);
  904. begin
  905.   {$IFDEF OnlineHelp}
  906.   Timer1.Enabled := false;
  907.  
  908.   // TODO: Insert a small online help hint
  909.   //Caption := gOnlineHelpWord;
  910.   {$ENDIF}
  911. end;
  912.  
  913. procedure TForm1.TreeView1DblClick(Sender: TObject);
  914. var
  915.   tn: TTreeNode;
  916.   lineNo: integer;
  917. begin
  918.   tn := TTreeView(Sender).Selected;
  919.   if tn = nil then exit;
  920.   lineNo := Integer(tn.Data);
  921.   if lineNo > 0 then GotoLineNo(lineNo);
  922. end;
  923.  
  924. (*
  925. {$IFDEF USE_SHDOCVW_TLB}
  926. *)
  927. procedure TForm1.WebBrowser1BeforeNavigate2(ASender: TObject;
  928.   const pDisp: IDispatch; const URL, Flags, TargetFrameName, PostData,
  929.   Headers: OleVariant; var Cancel: WordBool);
  930. begin
  931.   BeforeNavigate(URL, Cancel);
  932. end;
  933. (*
  934. {$ELSE}
  935. procedure TForm1.WebBrowser1BeforeNavigate2(ASender: TObject;
  936.   const pDisp: IDispatch; var URL, Flags, TargetFrameName, PostData,
  937.   Headers: OleVariant; var Cancel: WordBool);
  938. begin
  939.   BeforeNavigate(URL, Cancel);
  940. end;
  941. {$ENDIF}
  942. *)
  943.  
  944. procedure TForm1.WebBrowser1WindowClosing(ASender: TObject;
  945.   IsChildWindow: WordBool; var Cancel: WordBool);
  946. resourcestring
  947.   SCloseRequest = 'A script has requested the window to be closed. The window of a standalone script would now close.';
  948. begin
  949.   ShowMessage(SCloseRequest);
  950.   TWebBrowser(ASender).Clear;
  951.   Cancel := true;
  952. end;
  953.  
  954. procedure TForm1.BeforeNavigate(const URL: OleVariant; var Cancel: WordBool);
  955. var
  956.   s, myURL: string;
  957.   lineno: integer;
  958.   p: integer;
  959. begin
  960.   {$REGION 'Line number references (PHP errors and warnings)'}
  961.   if Copy(URL, 1, length(FASTPHP_GOTO_URI_PREFIX)) = FASTPHP_GOTO_URI_PREFIX then
  962.   begin
  963.     try
  964.       s := copy(URL, length(FASTPHP_GOTO_URI_PREFIX)+1, 99);
  965.       if not TryStrToInt(s, lineno) then exit;
  966.       GotoLineNo(lineno);
  967.       SynEditFocusTimer.Enabled := true;
  968.     finally
  969.       Cancel := true;
  970.     end;
  971.     Exit;
  972.   end;
  973.   {$ENDREGION}
  974.  
  975.   {$REGION 'Intelligent browser (executes PHP scripts which are clicked in a hyperlink)'}
  976.   if URL <> 'about:blank' then
  977.   begin
  978.     myUrl := URL;
  979.  
  980.     p := Pos('?', myUrl);
  981.     if p >= 1 then myURL := copy(myURL, 1, p-1);
  982.  
  983.     // TODO: myURL urldecode
  984.     // TODO: maybe we could even open that file in the editor!
  985.     // TODO: ?parameter=....
  986.  
  987.     if FileExists(myURL) and (EndsText('.php', myURL) or EndsText('.php3', myURL) or EndsText('.php4', myURL) or EndsText('.php5', myURL) or EndsText('.phps', myURL)) then
  988.     begin
  989.       WebBrowser1.LoadHTML(RunPHPScript(myURL), myUrl);
  990.       Cancel := true;
  991.     end;
  992.   end;
  993.   {$ENDREGION}
  994. end;
  995.  
  996. procedure TForm1.BtnLightClick(Sender: TObject);
  997. var
  998.   CanClose: boolean;
  999. begin
  1000.   FormCloseQuery(Form1, CanClose);
  1001.   if not CanClose then exit;
  1002.  
  1003.   if IsThemeDark then
  1004.   begin
  1005.     BtnLight.Picture.Assign(BtnLightOn.Picture);
  1006.     Theme_Light;
  1007.     TFastPHPConfig.DarkTheme := false;
  1008.   end
  1009.   else
  1010.   begin
  1011.     BtnLight.Picture.Assign(BtnLightOff.Picture);
  1012.     Theme_Dark;
  1013.     TFastPHPConfig.DarkTheme := true;
  1014.   end;
  1015. end;
  1016.  
  1017. procedure TForm1.BtnSpecialCharsClick(Sender: TObject);
  1018. var
  1019.   opts: TSynEditorOptions;
  1020. begin
  1021.   opts := SynEdit1.Options;
  1022.   if eoShowSpecialChars in SynEdit1.Options then
  1023.   begin
  1024.     BtnSpecialChars.Picture.Assign(BtnSpecialCharsOff.Picture);
  1025.     Exclude(opts, eoShowSpecialChars);
  1026.     TFastPHPConfig.SpecialChars := false;
  1027.   end
  1028.   else
  1029.   begin
  1030.     BtnSpecialChars.Picture.Assign(BtnSpecialCharsOn.Picture);
  1031.     Include(opts, eoShowSpecialChars);
  1032.     TFastPHPConfig.SpecialChars := true;
  1033.   end;
  1034.   SynEdit1.Options := opts;
  1035. end;
  1036.  
  1037. procedure TForm1.FileModTimerTimer(Sender: TObject);
  1038. resourcestring
  1039.   SChangeConflict = 'The file was changed in a different application BUT IT WAS ALSO MODIFIED HERE! Reload file AND LOSE CHANGES HERE?';
  1040.   SChangeNotify = 'The file was changed in a different application! Reload file?';
  1041. begin
  1042.   FileModTimer.Enabled := false;
  1043.   if FileModLast <> FileAge(GetScrapFile) then
  1044.   begin
  1045.     FileModLast := FileAge(GetScrapFile);
  1046.     if SynEdit1.Modified then
  1047.     begin
  1048.       if MessageDlg(SChangeConflict, mtWarning, mbYesNoCancel, 0) = mrYes then
  1049.       begin
  1050.         SynEdit1.Lines.LoadFromFile(GetScrapFile);
  1051.       end;
  1052.     end
  1053.     else
  1054.     begin
  1055.       if MessageDlg(SChangeNotify, mtConfirmation, mbYesNoCancel, 0) = mrYes then
  1056.       begin
  1057.         SynEdit1.Lines.LoadFromFile(GetScrapFile);
  1058.       end;
  1059.     end;
  1060.   end;
  1061.   FileModTimer.Enabled := true;
  1062. end;
  1063.  
  1064. procedure TForm1.FontSizeTimerTimer(Sender: TObject);
  1065. begin
  1066.   if Memo2.Font.Size <> (SynEdit1.Font.Size-3) then
  1067.     Memo2.Font.Size := (SynEdit1.Font.Size-3);
  1068. end;
  1069.  
  1070. procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
  1071. begin
  1072.   DragAcceptFiles(Handle, False);
  1073.   TFastPHPConfig.FontSize := SynEdit1.Font.Size;
  1074. end;
  1075.  
  1076. procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
  1077. var
  1078.   r: integer;
  1079. resourcestring
  1080.   SWantToSave = 'Do you want to save?';
  1081. begin
  1082.   if SynEdit1.Modified then
  1083.   begin
  1084.     if (ParamStr(1) <> '') or (FSaveAsFilename <> '') then
  1085.     begin
  1086.       r := MessageDlg(SWantToSave, mtConfirmation, mbYesNoCancel, 0);
  1087.       if r = mrCancel then
  1088.       begin
  1089.         CanClose := false;
  1090.         Exit;
  1091.       end
  1092.       else if r = mrYes then
  1093.       begin
  1094.         ActionSave.Execute;
  1095.         CanClose := true;
  1096.       end;
  1097.     end
  1098.     else
  1099.     begin
  1100.       ActionSave.Execute;
  1101.       CanClose := true;
  1102.     end;
  1103.   end;
  1104. end;
  1105.  
  1106. procedure TForm1.FormCreate(Sender: TObject);
  1107. var
  1108.   exeDir: string;
  1109.   sScrapFile: string;
  1110. begin
  1111.   HlpPrevPageIndex := -1;
  1112.   CurSearchTerm := '';
  1113.   sScrapFile := GetScrapFile;
  1114.   Caption := Caption + ' - ' + sScrapFile;
  1115.   Application.Title := Format('%s - FastPHP', [ExtractFileName(sScrapFile)]); // do not translate!
  1116.   SrcRep := TSynEditFindReplace.Create(self);
  1117.   SrcRep.Editor := SynEdit1;
  1118.   SynEdit1.Gutter.Gradient := HighColorWindows;
  1119.  
  1120.   Screen.Cursors[crMouseGutter] := LoadCursor(hInstance, 'MOUSEGUTTER');
  1121.   SynEdit1.Gutter.Cursor := crMouseGutter;
  1122.  
  1123.   exeDir := IncludeTrailingPathDelimiter(ExtractFilePath(ParamStr(0)));
  1124.   if FileExists(exeDir + 'codeexplorer.bmp') then ImageList1.LoadAndSplitImages(exeDir + 'codeexplorer.bmp');
  1125.  
  1126.   FileModLast := FileAge(sScrapFile);
  1127.   FileModTimer.Enabled := True;
  1128. end;
  1129.  
  1130. procedure TForm1.FormDestroy(Sender: TObject);
  1131. begin
  1132.   if Assigned(ChmIndex) then
  1133.   begin
  1134.     FreeAndNil(ChmIndex);
  1135.   end;
  1136.   FreeAndNil(SrcRep);
  1137.  
  1138.   if hMutex <> 0 then CloseHandle(hMutex); // Note: ReleaseMutex does not work as expected!
  1139.  
  1140.   if Assigned(codeExplorer) then
  1141.   begin
  1142.     codeExplorer.Terminate;
  1143.     codeExplorer.WaitFor;
  1144.     FreeAndNil(codeExplorer);
  1145.   end;
  1146. end;
  1147.  
  1148. procedure TForm1.FormShow(Sender: TObject);
  1149. var
  1150.   ScrapFile: string;
  1151.   tmpFontSize: integer;
  1152.   opts: TSynEditorOptions;
  1153. resourcestring
  1154.   SFileAlreadyOpen = 'File "%s" is alrady open!';
  1155. begin
  1156.   if FormShowRanOnce then exit; // If the theme is changed from normal to dark, OnShow will be called another time
  1157.   FormShowRanOnce := true;
  1158.  
  1159.   DoubleBuffered := true;
  1160.  
  1161.   ScrapFile := GetScrapFile;
  1162.   if ScrapFile = '' then
  1163.   begin
  1164.     Application.Terminate; // Close;
  1165.     exit;
  1166.   end;
  1167.  
  1168.   opts := SynEdit1.Options;
  1169.   if TFastPHPConfig.SpecialChars then
  1170.   begin
  1171.     BtnSpecialChars.Picture.Assign(BtnSpecialCharsOn.Picture);
  1172.     Include(opts, eoShowSpecialChars);
  1173.   end
  1174.   else
  1175.   begin
  1176.     BtnSpecialChars.Picture.Assign(BtnSpecialCharsOff.Picture);
  1177.     Exclude(opts, eoShowSpecialChars);
  1178.   end;
  1179.   SynEdit1.Options := opts;
  1180.  
  1181.   PageControl1.ActivePage := PlaintextTabSheet;
  1182.  
  1183.   PageControl2.ActivePage := CodeTabsheet;
  1184.   HelpTabsheet.TabVisible := false;
  1185.  
  1186.   tmpFontSize := TFastPHPConfig.FontSize;
  1187.   if tmpFontSize <> -1 then SynEdit1.Font.Size := tmpFontSize;
  1188.  
  1189.   if FileExists(ScrapFile) then
  1190.   begin
  1191.     if hMutex = 0 then
  1192.     begin
  1193.       hMutex := CreateMutex(nil, True, PChar('FastPHP'+md5(UpperCase(ScrapFile)))); // do not translate
  1194.       if GetLastError = ERROR_ALREADY_EXISTS then
  1195.       begin
  1196.         // TODO: It would be great if the window of that FastPHP instance would switched to foreground
  1197.         ShowMessageFmt(SFileAlreadyOpen, [ScrapFile]);
  1198.         Application.Terminate; // Close;
  1199.         exit;
  1200.       end;
  1201.  
  1202.       SynEdit1.Lines.LoadFromFile(ScrapFile);
  1203.     end;
  1204.   end
  1205.   else
  1206.     SynEdit1.Lines.Clear;
  1207.  
  1208.   SynEdit1.SetFocus;
  1209.  
  1210.   StartCodeExplorer;
  1211.  
  1212.   DragAcceptFiles(Handle, True);
  1213.  
  1214.   StartupTimer.Enabled := true;
  1215. end;
  1216.  
  1217. function TForm1.ContainsUnicodeCharsInAnsiFile: boolean;
  1218. var
  1219.   lines: TStringList;
  1220.   ms: TMemoryStream;
  1221. begin
  1222.   if SynEdit1.Lines.Encoding = TEncoding.UTF8 then
  1223.   begin
  1224.     result := false;
  1225.     exit;
  1226.   end;
  1227.  
  1228.   lines := TStringList.Create;
  1229.   ms := TMemoryStream.Create;
  1230.   try
  1231.     SynEdit1.Lines.SaveToStream(ms);
  1232.     ms.Position := 0;
  1233.     lines.LoadFromStream(ms);
  1234.     result := Trim(lines.Text) <> Trim(synedit1.Lines.Text);
  1235.   finally
  1236.     FreeAndNil(ms);
  1237.     FreeAndNil(lines);
  1238.   end;
  1239. end;
  1240.  
  1241. procedure TForm1.SaveToFile(filename: string);
  1242. var
  1243.   ss: TStringStream;
  1244.   ms: TMemoryStream;
  1245.   fs: TFileStream;
  1246.   eolStyle: string;
  1247.   str: string;
  1248.   UpgradeEncodingToUnicode: boolean;
  1249.   DoReloadEncoding: boolean;
  1250. resourcestring
  1251.   LNG_UpgradeToUtf8 = 'Unicode characters have been inserted to this ANSI encoded file. Convert file to UTF-8? (If you choose "No", the Unicode characters will be replaced with "?")';
  1252. begin
  1253.   FileModTimer.Enabled := false;
  1254.  
  1255.   if ContainsUnicodeCharsInAnsiFile then
  1256.   begin
  1257.     DoReloadEncoding := true;
  1258.     case MessageBox(Handle, PChar(LNG_UpgradeToUtf8), PChar(Caption), MB_YESNOCANCEL) of
  1259.       ID_YES:
  1260.       begin
  1261.         UpgradeEncodingToUnicode := true;
  1262.       end;
  1263.       ID_NO:
  1264.       begin
  1265.         UpgradeEncodingToUnicode := false;
  1266.       end;
  1267.       ID_CANCEL:
  1268.       begin
  1269.         Abort;
  1270.       end;
  1271.     end;
  1272.   end
  1273.   else
  1274.   begin
  1275.     DoReloadEncoding := false;
  1276.     UpgradeEncodingToUnicode := false;
  1277.   end;
  1278.  
  1279.   ms := TMemoryStream.Create;
  1280.   ss := TStringStream.Create('');
  1281.   fs := TFileStream.Create(filename, fmCreate);
  1282.   try
  1283.     // Save everything in a memory stream and then to a string
  1284.     // in comparison to "str := SynEdit1.Lines.Text;",
  1285.     // This approach should preserve LF / CRLF line endings
  1286.     if UpgradeEncodingToUnicode then
  1287.       SynEdit1.Lines.SaveToStream(ms, TEncoding.UTF8) // upgrade an ANSI file to UTF-8 (e.g. if you include Japanese characters)
  1288.     else
  1289.       SynEdit1.Lines.SaveToStream(ms);
  1290.     if DoReloadEncoding then
  1291.     begin
  1292.       ms.Position := 0;
  1293.       SynEdit1.Lines.LoadFromStream(ms);
  1294.     end;
  1295.     ms.Position := 0;
  1296.     ss.CopyFrom(ms, ms.Size);
  1297.     ss.Position := 0;
  1298.     str := ss.ReadString(ss.Size);
  1299.     ss.Size := 0; // clear string-stream, because we need it later again
  1300.  
  1301.     // Detect current line-endings
  1302.     if Copy(str, 1, 2) = '#!' then
  1303.     begin
  1304.       // Shebang. Use ONLY Linux LF
  1305.       str := StringReplace(str, #13#10, #10, [rfReplaceAll]);
  1306.       eolStyle := #10 // Linux LF
  1307.     end
  1308.     else
  1309.     begin
  1310.       if Pos(#13#10, str) > 0 then
  1311.         eolStyle := #13#10 // Windows CRLF
  1312.       else if Pos(#10, str) > 0 then
  1313.         eolStyle := #10 // Linux LF
  1314.       else
  1315.       begin
  1316.         if DefaultTextLineBreakStyle = tlbsLF then
  1317.           eolStyle := #10 // Linux LF
  1318.         else if DefaultTextLineBreakStyle = tlbsCRLF then
  1319.           eolStyle := #13#10 // Windows CRLF
  1320.         //else if DefaultTextLineBreakStyle = tlbsCR then
  1321.         //  eolStyle := #13 // Old Mac CR
  1322.         else
  1323.           eolStyle := #13#10; // (Should not happen)
  1324.       end;
  1325.     end;
  1326.  
  1327.     // Unitfy line-endings
  1328.     str := StringReplace(str, #13, '', [rfReplaceAll]);
  1329.     str := StringReplace(str, #10, eolStyle, [rfReplaceAll]);
  1330.  
  1331.     // Replace all trailing linebreaks by a single line break
  1332.     // Note: Removing all line breaks is not good, since Linux's "nano" will
  1333.     //       re-add a linebreak at the end of the file
  1334.     str := TrimRight(str) + eolStyle;
  1335.  
  1336.     // Old versions of Delphi/SynEdit write an UTF-8 BOM, which makes problems
  1337.     // e.g. with AJAX handlers (because AJAX reponses must not have a BOM).
  1338.     // So we try to avoid that.
  1339.     // Note that the output is still UTF-8 encoded if the input file was UTF-8 encoded
  1340.     if Copy(str,1,3) = #$EF#$BB#$BF then Delete(str, 1, 3);
  1341.  
  1342.     // Now save to the file
  1343.     ss.WriteString(str);
  1344.     ss.Position := 0;
  1345.     fs.CopyFrom(ss, ss.Size-ss.Position);
  1346.   finally
  1347.     FreeAndNil(ms);
  1348.     FreeAndNil(ss);
  1349.     FreeAndNil(fs);
  1350.   end;
  1351.  
  1352.   FileModLast := FileAge(GetScrapFile);
  1353.   FileModTimer.Enabled := True;
  1354. end;
  1355.  
  1356. procedure TForm1.StartCodeExplorer;
  1357. begin
  1358.   codeExplorer := TRunCodeExplorer.Create(true);
  1359.   codeExplorer.InputRequestCallback := InputRequestCallback;
  1360.   codeExplorer.OutputNotifyCallback := OutputNotifyCallback;
  1361.   codeExplorer.PhpExe := GetPHPExe;
  1362.   codeExplorer.PhpFile := IncludeTrailingPathDelimiter(ExtractFileDir(Application.ExeName)) + 'codeexplorer.php'; // GetScrapFile;
  1363.   codeExplorer.WorkDir := ExtractFileDir(Application.ExeName);
  1364.   codeExplorer.Resume;
  1365. end;
  1366.  
  1367. procedure TForm1.StartUpTimerTimer(Sender: TObject);
  1368. begin
  1369.   StartupTimer.Enabled := false;
  1370.  
  1371.   // We need this timer because we cannot change the Theme during OnShow,
  1372.   // because the Delphi VCL Theme is buggy!
  1373.  
  1374.   if TFastPHPConfig.DarkTheme then
  1375.   begin
  1376.     BtnLight.Picture.Assign(BtnLightOff.Picture);
  1377.     Theme_Dark;
  1378.   end
  1379.   else
  1380.   begin
  1381.     BtnLight.Picture.Assign(BtnLightOn.Picture);
  1382.     Theme_Light;
  1383.   end;
  1384. end;
  1385.  
  1386. function TForm1.GetScrapFile: string;
  1387. var
  1388.   tmpPath: string;
  1389. resourcestring
  1390.   SFileDoesNotExistsCreate = 'File %s does not exist. Create it?';
  1391.   SPathDoesNotExistTryAgain = 'Path does not exist! Please try again.';
  1392. begin
  1393.   if FSaveAsFilename <> '' then
  1394.   begin
  1395.     result := FSaveAsFilename;
  1396.     exit;
  1397.   end;
  1398.  
  1399.   if FScrapFile <> '' then
  1400.   begin
  1401.     result := FScrapFile;
  1402.     exit;
  1403.   end;
  1404.  
  1405.   if ParamStr(1) <> '' then
  1406.   begin
  1407.     // Program was started with a filename
  1408.  
  1409.     result := ParamStr(1);
  1410.  
  1411.     if not FileExists(result) then
  1412.     begin
  1413.       case MessageDlg(Format(SFileDoesNotExistsCreate, [result]), mtConfirmation, mbYesNoCancel, 0) of
  1414.         mrYes:
  1415.           try
  1416.             SaveToFile(result);
  1417.           except
  1418.             on E: Exception do
  1419.             begin
  1420.               MessageDlg(E.Message, mtError, [mbOk], 0);
  1421.               Application.Terminate;
  1422.               result := '';
  1423.               exit;
  1424.             end;
  1425.           end;
  1426.         mrNo:
  1427.           begin
  1428.             Application.Terminate;
  1429.             result := '';
  1430.             exit;
  1431.           end;
  1432.         mrCancel:
  1433.           begin
  1434.             Application.Terminate;
  1435.             result := '';
  1436.             exit;
  1437.           end;
  1438.       end;
  1439.     end;
  1440.   end
  1441.   else
  1442.   begin
  1443.     // Program is started without filename -> use scrap file
  1444.  
  1445.     result := TFastPHPConfig.ScrapFile;
  1446.  
  1447.     if not FileExists(result) then
  1448.     begin
  1449.       repeat
  1450.         {$REGION 'Determinate opendialog initial directory'}
  1451.         if result <> '' then
  1452.         begin
  1453.           tmpPath := ExtractFilePath(result);
  1454.           if DirectoryExists(tmpPath) then
  1455.           begin
  1456.             OpenDialog3.InitialDir := tmpPath;
  1457.             OpenDialog3.FileName := Result;
  1458.           end
  1459.           else
  1460.           begin
  1461.             OpenDialog3.InitialDir := GetMyDocumentsFolder;
  1462.           end;
  1463.         end
  1464.         else
  1465.         begin
  1466.           OpenDialog3.InitialDir := GetMyDocumentsFolder;
  1467.         end;
  1468.         {$ENDREGION}
  1469.  
  1470.         if not OpenDialog3.Execute then
  1471.         begin
  1472.           Application.Terminate;
  1473.           result := '';
  1474.           exit;
  1475.         end;
  1476.  
  1477.         if not DirectoryExists(ExtractFilePath(OpenDialog3.FileName)) then
  1478.         begin
  1479.           MessageDlg(SPathDoesNotExistTryAgain, mtWarning, [mbOk], 0);
  1480.         end
  1481.         else
  1482.         begin
  1483.           result := OpenDialog3.FileName;
  1484.         end;
  1485.       until result <> '';
  1486.  
  1487.       if not FileExists(result) then
  1488.       begin
  1489.         try
  1490.           // Try saving the file; check if we have permissions
  1491.           //SynEdit1.Lines.Clear;
  1492.           SaveToFile(result);
  1493.         except
  1494.           on E: Exception do
  1495.           begin
  1496.             MessageDlg(E.Message, mtError, [mbOk], 0);
  1497.             Application.Terminate;
  1498.             result := '';
  1499.             exit;
  1500.           end;
  1501.         end;
  1502.       end;
  1503.  
  1504.       TFastPHPConfig.ScrapFile := result;
  1505.       FScrapFile := result;
  1506.     end;
  1507.   end;
  1508. end;
  1509.  
  1510. procedure TForm1.Help;
  1511. var
  1512.   IndexFile, chmFile, w, OriginalWord, url: string;
  1513.   internalHtmlFile: string;
  1514. resourcestring
  1515.   SChmFileNotAValidPHPDocumentation = 'The CHM file is not a valid PHP documentation. Cannot use help.';
  1516.   SUnknownErrorCannotUseHelp = 'Unknown error. Cannot use help.';
  1517.   SNoHelpAvailable = 'No help for "%s" available';
  1518. begin
  1519.   if not Assigned(ChmIndex) then
  1520.   begin
  1521.     IndexFile := TFastPHPConfig.HelpIndex;
  1522.     IndexFile := ChangeFileExt(IndexFile, '.ini'); // Just to be sure. Maybe someone wrote manually the ".chm" file in there
  1523.     if FileExists(IndexFile) then
  1524.     begin
  1525.       ChmIndex := TMemIniFile.Create(IndexFile);
  1526.     end;
  1527.   end;
  1528.  
  1529.   if Assigned(ChmIndex) then
  1530.   begin
  1531.     IndexFile := TFastPHPConfig.HelpIndex;
  1532.     // We don't check if IndexFile still exists. It is not important since we have ChmIndex pre-loaded in memory
  1533.  
  1534.     chmFile := ChangeFileExt(IndexFile, '.chm');
  1535.     if not FileExists(chmFile) then
  1536.     begin
  1537.       FreeAndNil(ChmIndex);
  1538.     end;
  1539.   end;
  1540.  
  1541.   if not Assigned(ChmIndex) then
  1542.   begin
  1543.     if not OpenDialog1.Execute then exit;
  1544.  
  1545.     chmFile := OpenDialog1.FileName;
  1546.     if not FileExists(chmFile) then exit;
  1547.  
  1548.     IndexFile := ChangeFileExt(chmFile, '.ini');
  1549.  
  1550.     if not FileExists(IndexFile) then
  1551.     begin
  1552.       Panel1.Align := alClient;
  1553.       Panel1.Visible := true;
  1554.       Panel1.BringToFront;
  1555.       Screen.Cursor := crHourGlass;
  1556.       Application.ProcessMessages;
  1557.       try
  1558.         if not ParseCHM(chmFile) then
  1559.         begin
  1560.           MessageDlg(SChmFileNotAValidPHPDocumentation, mtError, [mbOk], 0);
  1561.           exit;
  1562.         end;
  1563.       finally
  1564.         Screen.Cursor := crDefault;
  1565.         Panel1.Visible := false;
  1566.       end;
  1567.  
  1568.       if not FileExists(IndexFile) then
  1569.       begin
  1570.         MessageDlg(SUnknownErrorCannotUseHelp, mtError, [mbOk], 0);
  1571.         exit;
  1572.       end;
  1573.     end;
  1574.  
  1575.     TFastPHPConfig.HelpIndex := IndexFile;
  1576.  
  1577.     ChmIndex := TMemIniFile.Create(IndexFile);
  1578.   end;
  1579.  
  1580.   w := GetWordUnderCaret(SynEdit1);
  1581.   if w = '' then exit;
  1582.   {$IFDEF UNICODE}
  1583.   if CharInSet(w[1], ['0'..'9']) then exit;
  1584.   {$ELSE}
  1585.   if w[1] in ['0'..'9'] then exit;
  1586.   {$ENDIF}
  1587.  
  1588.   Originalword := w;
  1589. //  w := StringReplace(w, '_', '-', [rfReplaceAll]);
  1590.   w := LowerCase(w);
  1591.   CurSearchTerm := w;
  1592.  
  1593.   internalHtmlFile := ChmIndex.ReadString('function', CurSearchTerm, ''); // do not translate
  1594.   if internalHtmlFile = '' then
  1595.     internalHtmlFile := ChmIndex.ReadString('_HelpWords_', CurSearchTerm, ''); // do not translate
  1596.   if internalHtmlFile = '' then
  1597.   begin
  1598.     HelpTabsheet.TabVisible := false;
  1599.     HlpPrevPageIndex := -1;
  1600.     ShowMessageFmt(SNoHelpAvailable, [Originalword]);
  1601.     Exit;
  1602.   end;
  1603.  
  1604.   url := 'mk:@MSITStore:'+ChmFile+'::'+internalHtmlFile;
  1605.  
  1606.   HlpPrevPageIndex := PageControl2.ActivePageIndex; // Return by pressing ESC
  1607.   HelpTabsheet.TabVisible := true;
  1608.   PageControl2.ActivePage := HelpTabsheet;
  1609.   WebBrowser2.Navigate(url);
  1610.   WebBrowser2.Wait;
  1611. end;
  1612.  
  1613. procedure TForm1.GotoLineNo(LineNo:integer);
  1614. var
  1615.   line: string;
  1616.   i: integer;
  1617. begin
  1618.   SynEdit1.GotoLineAndCenter(LineNo);
  1619.  
  1620.   // Skip indent
  1621.   line := SynEdit1.Lines[SynEdit1.CaretY];
  1622.   for i := 1 to Length(line) do
  1623.   begin
  1624.     {$IFDEF UNICODE}
  1625.     if not CharInSet(line[i], [' ', #9]) then
  1626.     {$ELSE}
  1627.     if not (line[i] in [' ', #9]) then
  1628.     {$ENDIF}
  1629.     begin
  1630.       SynEdit1.CaretX := i-1;
  1631.       break;
  1632.     end;
  1633.   end;
  1634.  
  1635.   PageControl2.ActivePage := CodeTabsheet;
  1636.   if SynEdit1.CanFocus then SynEdit1.SetFocus;
  1637. end;
  1638.  
  1639. procedure TForm1.PageControl2Changing(Sender: TObject;
  1640.   var AllowChange: Boolean);
  1641. begin
  1642.   if PageControl2.ActivePage = HelpTabsheet then
  1643.     HlpPrevPageIndex := -1
  1644.   else
  1645.     HlpPrevPageIndex := PageControl2.ActivePageIndex;
  1646.  
  1647.   AllowChange := true;
  1648. end;
  1649.  
  1650. procedure TForm1.Memo2DblClick(Sender: TObject);
  1651. var
  1652.   line: string;
  1653.  
  1654.   procedure _process(toFind: string);
  1655.   var
  1656.     p, lineno: integer;
  1657.   begin
  1658.     if FileSystemCaseSensitive then
  1659.       p := Pos(toFind, line)
  1660.     else
  1661.       p := Pos(LowerCase(toFind), LowerCase(line));
  1662.     if p <> 0 then
  1663.     begin
  1664.       line := copy(line, p+length(toFind), 99); // TODO: "(test.php:123)" does not work, because "123)" will be tried to convert to Integer
  1665.       if not TryStrToInt(line, lineno) then exit;
  1666.       GotoLineNo(lineno);
  1667.     end;
  1668.   end;
  1669.  
  1670. begin
  1671.   line := memo2.Lines.Strings[Memo2.CaretPos.Y];
  1672.  
  1673.   {$REGION 'Possibility 1: filename.php:lineno'}
  1674.   _process(ExtractFileName(GetScrapFile) + ':');
  1675.   {$ENDREGION}
  1676.  
  1677.   {$REGION 'Possibility 2: on line xx'}
  1678.   _process(ExtractFileName(GetScrapFile) + ' on line '); // do not translate!
  1679.   {$ENDREGION}
  1680. end;
  1681.  
  1682. procedure TForm1.Memo2KeyDown(Sender: TObject; var Key: Word;
  1683.   Shift: TShiftState);
  1684. begin
  1685.   if ((ssCtrl in Shift) and (Key = 65)) then TMemo(Sender).SelectAll;
  1686. end;
  1687.  
  1688. function TForm1.MarkUpLineReference(cont: string): string;
  1689.  
  1690.   procedure _process(toFind: string);
  1691.   var
  1692.     p, a, b: integer;
  1693.     num: integer;
  1694.     insert_a, insert_b: string;
  1695.   begin
  1696.     if FileSystemCaseSensitive then
  1697.       p := Pos(toFind, cont)
  1698.     else
  1699.       p := Pos(LowerCase(toFind), LowerCase(cont));
  1700.     while p >= 1 do
  1701.     begin
  1702.       a := p;
  1703.       b := p + length(toFind);
  1704.       num := 0;
  1705.       {$IFDEF UNICODE}
  1706.       while CharInSet(cont[b], ['0'..'9']) do
  1707.       {$ELSE}
  1708.       while cont[b] in ['0'..'9'] do
  1709.       {$ENDIF}
  1710.       begin
  1711.         num := num*10 + StrToInt(cont[b]);
  1712.         inc(b);
  1713.       end;
  1714.  
  1715.       insert_b := '</a>';
  1716.       insert_a := '<a href="' + FASTPHP_GOTO_URI_PREFIX + IntToStr(num) + '">';
  1717.  
  1718.       insert(insert_b, cont, b);
  1719.       insert(insert_a, cont, a);
  1720.  
  1721.       p := b + Length(insert_a) + Length(insert_b);
  1722.  
  1723.       p := PosEx(toFind, cont, p+1);
  1724.     end;
  1725.   end;
  1726.  
  1727. begin
  1728.   {$REGION 'Possibility 1: filename.php:lineno'}
  1729.   _process(ExtractFileName(GetScrapFile) + ':');
  1730.   {$ENDREGION}
  1731.  
  1732.   {$REGION 'Possibility 2: on line xx'}
  1733.   _process(ExtractFileName(GetScrapFile) + ' on line '); // do not translate!
  1734.   {$ENDREGION}
  1735.  
  1736.   result := cont;
  1737. end;
  1738.  
  1739. function TForm1.InputRequestCallback(var data: AnsiString): boolean;
  1740. begin
  1741.   data := UTF8Encode(SynEdit1.Text);
  1742.   result := true;
  1743. end;
  1744.  
  1745. function TForm1.IsThemeDark: boolean;
  1746. begin
  1747.   result := Assigned(TStyleManager.ActiveStyle) and (TStyleManager.ActiveStyle.Name<>'Windows');
  1748. end;
  1749.  
  1750. function TForm1.OutputNotifyCallback(const data: AnsiString): boolean;
  1751. begin
  1752.   result := TreeView1.FillWithFastPHPData(string(data));
  1753. end;
  1754.  
  1755. end.
  1756.