Subversion Repositories fastphp

Rev

Rev 104 | 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, WebBrowserUtils;
  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, 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.         SynEdit1.Modified := true; // seems to be required by Delphi 10.x
  464.         RefreshModifySign;
  465.       end;
  466.     end;
  467.  
  468.     function SpacesAvailable(lines: {$IFDEF UNICODE}TStrings{$ELSE}TUnicodeStrings{$ENDIF}): boolean;
  469.     var
  470.       i, spaces: integer;
  471.     begin
  472.       for i := 0 to lines.Count-1 do
  473.       begin
  474.         spaces := SpacesAtBeginning(lines.Strings[i]);
  475.         if spaces > 0 then
  476.         begin
  477.           result := true;
  478.           exit;
  479.         end;
  480.       end;
  481.       result := false;
  482.       exit;
  483.     end;
  484.  
  485. var
  486.   val: string;
  487.   ind: integer;
  488. resourcestring
  489.   SNoLinesAvailable = 'No lines with spaces at the beginning available';
  490.   SSpacesToLabs = 'Spaces to tabs';
  491.   SIndent = 'Indent:';
  492. begin
  493.   // TODO: if something is selected, only process the selected part
  494.  
  495.   if not SpacesAvailable(SynEdit1.Lines) then
  496.   begin
  497.     MessageDlg(SNoLinesAvailable, mtInformation, [mbOk], 0);
  498.     exit;
  499.   end;
  500.  
  501.   ind := GuessIndent(SynEdit1.Lines);
  502.   if ind <> -1 then val := IntToStr(ind);
  503.  
  504.   if not InputQuery(SSpacesToLabs, SIndent, val) or
  505.      not TryStrToInt(Trim(val), ind) then
  506.   begin
  507.     if SynEdit1.CanFocus then SynEdit1.SetFocus;
  508.     exit;
  509.   end;
  510.  
  511.   if ind = 0 then exit;
  512.   SpaceToTab(SynEdit1.Lines, ind);
  513. end;
  514.  
  515. procedure TForm1.ActionESCExecute(Sender: TObject);
  516. begin
  517.   if (HlpPrevPageIndex <> -1) and (PageControl2.ActivePage = HelpTabSheet) and
  518.      (HelpTabsheet.TabVisible) then
  519.   begin
  520.     PageControl2.ActivePageIndex := HlpPrevPageIndex;
  521.     HelpTabsheet.TabVisible := false;
  522.   end;
  523.  
  524.   // Dirty hack...
  525.   SrcRep.CloseDialogs;
  526. end;
  527.  
  528. procedure TForm1.ActionFindExecute(Sender: TObject);
  529. begin
  530.   SrcRep.FindExecute;
  531. end;
  532.  
  533. procedure TForm1.Run(Sender: TObject);
  534. var
  535.   bakTS: TTabSheet;
  536.   //ss: TStringStream;
  537.   //bakPos: Int64;
  538. begin
  539.   memo2.Lines.Text := '';
  540.  
  541.   if not BrowserLoadedOnce then
  542.   begin
  543.     bakTS := PageControl1.ActivePage;
  544.     try
  545.       PageControl1.ActivePage := HtmlTabSheet; // Required for the first time, otherwise, WebBrowser1.Clear will hang
  546.       Webbrowser1.Clear;
  547.     finally
  548.       PageControl1.ActivePage := bakTS;
  549.     end;
  550.     BrowserLoadedOnce := true;
  551.   end
  552.   else
  553.     Webbrowser1.Clear;
  554.  
  555.   Screen.Cursor := crHourGlass; // TODO: Doesn't work with F9
  556.   Application.ProcessMessages;
  557.  
  558.   try
  559.     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.
  560.  
  561.     // 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
  562.     // TODO 70422 * <fastphp> when a script has an endless loop, i want to have a possibility to cancel it
  563.     if SynEdit1.Lines.Encoding = TEncoding.UTF8 then
  564.       memo2.Lines.Text := Utf8Decode(RunPHPScript(GetScrapFile, Sender=ActionLint, False)) // if we have a UTF-8 file, then the DOS output is double-UTF8 encoded
  565.     else
  566.       memo2.Lines.Text := RunPHPScript(GetScrapFile, Sender=ActionLint, False);
  567.  
  568.     {$REGION 'Show in Web Browser'}
  569.     if SynEdit1.Lines.Encoding = TEncoding.UTF8 then
  570.       Webbrowser1.LoadHTML(MarkUpLineReference('<meta charset="utf-8">'+memo2.Lines.Text), GetScrapFile)
  571.     else
  572.       Webbrowser1.LoadHTML(MarkUpLineReference(memo2.Lines.Text), GetScrapFile);
  573.  
  574.     // Alternatively:
  575.     (*
  576.     ss := TstringStream.Create;
  577.     ss.WriteString(MarkUpLineReference(memo2.Lines.Text));
  578.     ss.Position := 0;
  579.     Webbrowser1.LoadStream(ss, GetScrapFile);
  580.     Webbrowser1.Wait;
  581.     ss.Free;
  582.     *)
  583.     {$ENDREGION}
  584.  
  585.     if IsTextHTML(memo2.lines.text) then
  586.       PageControl1.ActivePage := HtmlTabSheet
  587.     else
  588.       PageControl1.ActivePage := PlaintextTabSheet;
  589.   finally
  590.     Screen.Cursor := crDefault;
  591.   end;
  592. end;
  593.  
  594. procedure TForm1.RunConsole(Sender: TObject);
  595. begin
  596.   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.
  597.   RunPHPScript(GetScrapFile, Sender=ActionLint, True);
  598. end;
  599.  
  600. procedure TForm1.SynEdit1DropFiles(Sender: TObject; X, Y: Integer;
  601.   AFiles: TStrings);
  602. var
  603.   FileName: string;
  604. const
  605.   WARN_FILE_COUNT = 10;
  606. resourcestring
  607.   SAreYouSure = 'Are you sure you want to open %d files?';
  608. begin
  609.   if AFiles.Count > WARN_FILE_COUNT then
  610.   begin
  611.     if not MessageDlg(Format(SAreYouSure, [WARN_FILE_COUNT]), mtConfirmation, mbYesNoCancel, 0) <> mrYes then
  612.       exit;
  613.   end;
  614.  
  615.   for FileName in AFiles do
  616.   begin
  617.     ShellExecute(0, 'open', PChar(ParamStr(0)), PChar('"' + FileName + '"'), '', SW_NORMAL);
  618.   end;
  619. end;
  620.  
  621. procedure TForm1.SynEdit1GutterClick(Sender: TObject; Button: TMouseButton; X,
  622.   Y, Line: Integer; Mark: TSynEditMark);
  623. begin
  624.   (*
  625.   TSynEdit(Sender).CaretX := 1;
  626.   TSynEdit(Sender).CaretY := Line;
  627.   TSynEdit(Sender).SelLength := Length(TSynEdit(Sender).LineText);
  628.   *)
  629. end;
  630.  
  631. procedure TForm1.SynEdit1KeyDown(Sender: TObject; var Key: Word;
  632.   Shift: TShiftState);
  633. begin
  634.   if (Shift = [ssCtrl, ssShift]) and (Key = ord('C')) then
  635.   begin
  636.     // Disable "Column Select Mode" (https://github.com/SynEdit/SynEdit/blob/master/Source/SynEditKeyCmds.pas#L879)
  637.     // which can be enabled by pressing Ctrl+Shift+C
  638.     // Reasons why we disable it:
  639.     // 1. I think nobody needs this, and sometimes you accidentally press it
  640.     // 2. Ctrl+Shift+L would the combination to disable column select mode,
  641.     //    but you cannot use it, because it is already in use for linting
  642.     Key := 0;
  643.   end;
  644. end;
  645.  
  646. procedure TForm1.SynEdit1MouseCursor(Sender: TObject; const aLineCharPos: TBufferCoord; var aCursor: TCursor);
  647. {$IFDEF OnlineHelp}
  648. var
  649.   Line: Integer;
  650.   Column: Integer;
  651.   word: string;
  652. begin
  653.   Line  := aLineCharPos.Line-1;
  654.   Column := aLineCharPos.Char-1;
  655.   word := GetWordUnderPos(TSynEdit(Sender), Line, Column);
  656.   if word <> gOnlineHelpWord then
  657.   begin
  658.     gOnlineHelpWord := word;
  659.     Timer1.Enabled := false;
  660.     Timer1.Enabled := true;
  661.   end;
  662. {$ELSE}
  663. begin
  664. {$ENDIF}
  665. end;
  666.  
  667. procedure TForm1.SynEdit1MouseWheelDown(Sender: TObject; Shift: TShiftState;
  668.   MousePos: TPoint; var Handled: Boolean);
  669. begin
  670.   if ssCtrl in Shift then
  671.   begin
  672.     SynEdit1.Font.Size := Max(SynEdit1.Font.Size - 1, 5);
  673.     Handled := true;
  674.   end
  675.   else Handled := false;
  676. end;
  677.  
  678. procedure TForm1.SynEdit1MouseWheelUp(Sender: TObject; Shift: TShiftState;
  679.   MousePos: TPoint; var Handled: Boolean);
  680. begin
  681.   if ssCtrl in Shift then
  682.   begin
  683.     SynEdit1.Font.Size := SynEdit1.Font.Size + 1;
  684.     Handled := true;
  685.   end
  686.   else Handled := false;
  687. end;
  688.  
  689. procedure TForm1.SynEdit1PaintTransient(Sender: TObject; Canvas: TCanvas; TransientType: TTransientType);
  690. var
  691.   Editor: TSynEdit;
  692.   OpenChars: array of WideChar;//[0..2] of WideChar=();
  693.   CloseChars: array of WideChar;//[0..2] of WideChar=();
  694.  
  695.   function IsCharBracket(AChar: WideChar): Boolean;
  696.   begin
  697.     case AChar of
  698.       '{','[','(','<','}',']',')','>':
  699.         Result := True;
  700.       else
  701.         Result := False;
  702.     end;
  703.   end;
  704.  
  705.   function CharToPixels(P: TBufferCoord): TPoint;
  706.   begin
  707.     Result := Editor.RowColumnToPixels(Editor.BufferToDisplayPos(P));
  708.   end;
  709.  
  710. var
  711.   COLOR_FG: TColor;
  712.   COLOR_BG: TColor;
  713.   P: TBufferCoord;
  714.   Pix: TPoint;
  715.   D: TDisplayCoord;
  716.   S: UnicodeString;
  717.   I: Integer;
  718.   Attri: TSynHighlighterAttributes;
  719.   ArrayLength: Integer;
  720.   start: Integer;
  721.   TmpCharA, TmpCharB: WideChar;
  722. begin
  723.   // Source: https://github.com/SynEdit/SynEdit/blob/master/Demos/OnPaintTransientDemo/Unit1.pas
  724.  
  725.   if IsThemeDark then
  726.   begin
  727.     COLOR_FG := clLime;
  728.     COLOR_BG := clGreen;
  729.   end
  730.   else
  731.   begin
  732.     COLOR_FG := clGreen;
  733.     COLOR_BG := clLime;
  734.   end;
  735.  
  736.   if TSynEdit(Sender).SelAvail then exit;
  737.   Editor := TSynEdit(Sender);
  738.   ArrayLength:= 3;
  739.  
  740.   (*
  741.   if (Editor.Highlighter = shHTML) or (Editor.Highlighter = shXML) then
  742.     inc(ArrayLength);
  743.   *)
  744.  
  745.   SetLength(OpenChars, ArrayLength);
  746.   SetLength(CloseChars, ArrayLength);
  747.   for i := 0 to ArrayLength - 1 do
  748.   begin
  749.     case i of
  750.       0: begin OpenChars[i] := '('; CloseChars[i] := ')'; end;
  751.       1: begin OpenChars[i] := '{'; CloseChars[i] := '}'; end;
  752.       2: begin OpenChars[i] := '['; CloseChars[i] := ']'; end;
  753.       3: begin OpenChars[i] := '<'; CloseChars[i] := '>'; end;
  754.     end;
  755.   end;
  756.  
  757.   P := Editor.CaretXY;
  758.   D := Editor.DisplayXY;
  759.  
  760.   Start := Editor.SelStart;
  761.  
  762.   if (Start > 0) and (Start <= length(Editor.Text)) then
  763.     TmpCharA := Editor.Text[Start]
  764.   else
  765.     TmpCharA := #0;
  766.  
  767.   if (Start > 0){Added by VTS} and (Start < length(Editor.Text)) then
  768.     TmpCharB := Editor.Text[Start + 1]
  769.   else
  770.     TmpCharB := #0;
  771.  
  772.   if not IsCharBracket(TmpCharA) and not IsCharBracket(TmpCharB) then exit;
  773.   S := TmpCharB;
  774.   if not IsCharBracket(TmpCharB) then
  775.   begin
  776.     P.Char := P.Char - 1;
  777.     S := TmpCharA;
  778.   end;
  779.   Editor.GetHighlighterAttriAtRowCol(P, S, Attri);
  780.  
  781.   if (Editor.Highlighter.SymbolAttribute = Attri) then
  782.   begin
  783.     for i := low(OpenChars) to High(OpenChars) do
  784.     begin
  785.       if (S = OpenChars[i]) or (S = CloseChars[i]) then
  786.       begin
  787.         Pix := CharToPixels(P);
  788.  
  789.         Editor.Canvas.Brush.Style := bsSolid;//Clear;
  790.         Editor.Canvas.Font.Assign(Editor.Font);
  791.         Editor.Canvas.Font.Style := Attri.Style;
  792.  
  793.         if (TransientType = ttAfter) then
  794.         begin
  795.           Editor.Canvas.Font.Color := COLOR_FG;
  796.           Editor.Canvas.Brush.Color := COLOR_BG;
  797.         end
  798.         else
  799.         begin
  800.           Editor.Canvas.Font.Color := Attri.Foreground;
  801.           Editor.Canvas.Brush.Color := Attri.Background;
  802.         end;
  803.         if Editor.Canvas.Font.Color = clNone then
  804.           Editor.Canvas.Font.Color := Editor.Font.Color;
  805.         if Editor.Canvas.Brush.Color = clNone then
  806.           Editor.Canvas.Brush.Color := Editor.Color;
  807.  
  808.         Editor.Canvas.TextOut(Pix.X, Pix.Y, S);
  809.         P := Editor.GetMatchingBracketEx(P);
  810.  
  811.         if (P.Char > 0) and (P.Line > 0) then
  812.         begin
  813.           Pix := CharToPixels(P);
  814.           {$IF CompilerVersion >= 35.0} // Added by ViaThinkSoft (not sure about the exact version)
  815.           if Pix.X > Editor.Gutter.RealGutterWidth then // Added by ViaThinkSoft (not sure about the exact version)
  816.           {$ELSE}
  817.           if Pix.X > Editor.Gutter.Width then
  818.           {$IFEND}
  819.           begin
  820.             {$REGION 'Added by ViaThinkSoft'}
  821.             if (TransientType = ttAfter) then
  822.             begin
  823.               Editor.Canvas.Font.Color := COLOR_FG;
  824.               Editor.Canvas.Brush.Color := COLOR_BG;
  825.             end
  826.             else
  827.             begin
  828.               Editor.Canvas.Font.Color := Attri.Foreground;
  829.               Editor.Canvas.Brush.Color := Attri.Background;
  830.             end;
  831.             if Editor.Canvas.Font.Color = clNone then
  832.               Editor.Canvas.Font.Color := Editor.Font.Color;
  833.             if Editor.Canvas.Brush.Color = clNone then
  834.               Editor.Canvas.Brush.Color := Editor.Color;
  835.             {$ENDREGION}
  836.             if S = OpenChars[i] then
  837.               Editor.Canvas.TextOut(Pix.X, Pix.Y, CloseChars[i])
  838.             else Editor.Canvas.TextOut(Pix.X, Pix.Y, OpenChars[i]);
  839.           end;
  840.         end;
  841.       end;
  842.     end;
  843.     Editor.Canvas.Brush.Style := bsSolid;
  844.   end;
  845. end;
  846.  
  847. procedure TForm1.SynEdit1StatusChange(Sender: TObject;
  848.   Changes: TSynStatusChanges);
  849. begin
  850.   if scModified in Changes then
  851.     RefreshModifySign;
  852. end;
  853.  
  854. procedure TForm1.SynEditFocusTimerTimer(Sender: TObject);
  855. begin
  856.   SynEditFocusTimer.Enabled := false;
  857.   Button1.SetFocus; // Workaround for weird bug... This (and the timer) is necessary to get the focus to SynEdit1
  858.   SynEdit1.SetFocus;
  859. end;
  860.  
  861. procedure TForm1.Theme_Dark;
  862. begin
  863.   if IsThemeDark then exit;
  864.   TStyleManager.TrySetStyle('Windows10 SlateGray'); // do not translate
  865.   Color := 1316887;
  866.   Font.Color := clCream;
  867.   //Memo2.Font.Color := clCream;
  868.   //Memo2.ParentColor := true;
  869.   SynEdit1.ActiveLineColor := 2238502;
  870.   SynEdit1.Color := 1316887;
  871.   SynEdit1.Font.Color := clCream;
  872.   SynEdit1.Gutter.Color := 1316887;
  873.   SynEdit1.Gutter.Font.Color := clCream;
  874.   SynEdit1.Gutter.GradientStartColor := 2238502;
  875.   SynEdit1.Gutter.GradientEndColor := 1316887;
  876.   SynPHPSyn1.CommentAttri.Foreground := $00837B82;
  877.   SynPHPSyn1.IdentifierAttri.Foreground := 9627120;
  878.   SynPHPSyn1.KeyAttri.Foreground := 4157595;
  879.   SynPHPSyn1.NumberAttri.Foreground := 5008079;
  880.   SynPHPSyn1.StringAttri.Foreground := 6987151;
  881.   SynPHPSyn1.SymbolAttri.Foreground := 8769754;
  882.   SynPHPSyn1.VariableAttri.Foreground := 6924493;
  883. end;
  884.  
  885. procedure TForm1.Theme_Light;
  886. begin
  887.   if not IsThemeDark then exit;
  888.   TStyleManager.TrySetStyle('Windows'); // do not translate
  889.   Color := clBtnFace;
  890.   Font.Color := clWindowText;
  891.   //Memo2.Font.Color := clWindowText;
  892.   SynEdit1.ActiveLineColor := 14680010;
  893.   SynEdit1.Color := clCream;
  894.   SynEdit1.Font.Color := clWindowText;
  895.   SynEdit1.Gutter.Color := clBtnFace;
  896.   SynEdit1.Gutter.Font.Color := clWindowText;
  897.   SynEdit1.Gutter.GradientStartcolor := cl3dLight;
  898.   SynEdit1.Gutter.GradientEndColor := clBtnFace;;
  899.   SynPHPSyn1.CommentAttri.Foreground := 33023;
  900.   SynPHPSyn1.IdentifierAttri.Foreground := 4194304;
  901.   SynPHPSyn1.KeyAttri.Foreground := 4227072;
  902.   SynPHPSyn1.NumberAttri.Foreground := 213;
  903.   SynPHPSyn1.StringAttri.Foreground := 13762560;
  904.   SynPHPSyn1.SymbolAttri.Foreground := 4227072;
  905.   SynPHPSyn1.VariableAttri.Foreground := 213;
  906. end;
  907.  
  908. procedure TForm1.Timer1Timer(Sender: TObject);
  909. begin
  910.   {$IFDEF OnlineHelp}
  911.   Timer1.Enabled := false;
  912.  
  913.   // TODO: Insert a small online help hint
  914.   //Caption := gOnlineHelpWord;
  915.   {$ENDIF}
  916. end;
  917.  
  918. procedure TForm1.TreeView1DblClick(Sender: TObject);
  919. var
  920.   tn: TTreeNode;
  921.   lineNo: integer;
  922. begin
  923.   tn := TTreeView(Sender).Selected;
  924.   if tn = nil then exit;
  925.   lineNo := Integer(tn.Data);
  926.   if lineNo > 0 then GotoLineNo(lineNo);
  927. end;
  928.  
  929. (*
  930. {$IFDEF USE_SHDOCVW_TLB}
  931. *)
  932. procedure TForm1.WebBrowser1BeforeNavigate2(ASender: TObject;
  933.   const pDisp: IDispatch; const URL, Flags, TargetFrameName, PostData,
  934.   Headers: OleVariant; var Cancel: WordBool);
  935. begin
  936.   BeforeNavigate(URL, Cancel);
  937. end;
  938. (*
  939. {$ELSE}
  940. procedure TForm1.WebBrowser1BeforeNavigate2(ASender: TObject;
  941.   const pDisp: IDispatch; var URL, Flags, TargetFrameName, PostData,
  942.   Headers: OleVariant; var Cancel: WordBool);
  943. begin
  944.   BeforeNavigate(URL, Cancel);
  945. end;
  946. {$ENDIF}
  947. *)
  948.  
  949. procedure TForm1.WebBrowser1WindowClosing(ASender: TObject;
  950.   IsChildWindow: WordBool; var Cancel: WordBool);
  951. resourcestring
  952.   SCloseRequest = 'A script has requested the window to be closed. The window of a standalone script would now close.';
  953. begin
  954.   ShowMessage(SCloseRequest);
  955.   TWebBrowser(ASender).Clear;
  956.   Cancel := true;
  957. end;
  958.  
  959. procedure TForm1.BeforeNavigate(const URL: OleVariant; var Cancel: WordBool);
  960. var
  961.   s, myURL: string;
  962.   lineno: integer;
  963.   p: integer;
  964. begin
  965.   {$REGION 'Line number references (PHP errors and warnings)'}
  966.   if Copy(URL, 1, length(FASTPHP_GOTO_URI_PREFIX)) = FASTPHP_GOTO_URI_PREFIX then
  967.   begin
  968.     try
  969.       s := copy(URL, length(FASTPHP_GOTO_URI_PREFIX)+1, 99);
  970.       if not TryStrToInt(s, lineno) then exit;
  971.       GotoLineNo(lineno);
  972.       SynEditFocusTimer.Enabled := true;
  973.     finally
  974.       Cancel := true;
  975.     end;
  976.     Exit;
  977.   end;
  978.   {$ENDREGION}
  979.  
  980.   {$REGION 'Intelligent browser (executes PHP scripts which are clicked in a hyperlink)'}
  981.   if URL <> 'about:blank' then
  982.   begin
  983.     myUrl := URL;
  984.  
  985.     p := Pos('?', myUrl);
  986.     if p >= 1 then myURL := copy(myURL, 1, p-1);
  987.  
  988.     // TODO: myURL urldecode
  989.     // TODO: maybe we could even open that file in the editor!
  990.     // TODO: ?parameter=....
  991.  
  992.     if FileExists(myURL) and (EndsText('.php', myURL) or EndsText('.php3', myURL) or EndsText('.php4', myURL) or EndsText('.php5', myURL) or EndsText('.phps', myURL)) then
  993.     begin
  994.       WebBrowser1.LoadHTML(RunPHPScript(myURL), myUrl);
  995.       Cancel := true;
  996.     end;
  997.   end;
  998.   {$ENDREGION}
  999. end;
  1000.  
  1001. procedure TForm1.BtnLightClick(Sender: TObject);
  1002. var
  1003.   CanClose: boolean;
  1004. begin
  1005.   FormCloseQuery(Form1, CanClose);
  1006.   if not CanClose then exit;
  1007.  
  1008.   if IsThemeDark then
  1009.   begin
  1010.     BtnLight.Picture.Assign(BtnLightOn.Picture);
  1011.     Theme_Light;
  1012.     TFastPHPConfig.DarkTheme := false;
  1013.   end
  1014.   else
  1015.   begin
  1016.     BtnLight.Picture.Assign(BtnLightOff.Picture);
  1017.     Theme_Dark;
  1018.     TFastPHPConfig.DarkTheme := true;
  1019.   end;
  1020. end;
  1021.  
  1022. procedure TForm1.BtnSpecialCharsClick(Sender: TObject);
  1023. var
  1024.   opts: TSynEditorOptions;
  1025. begin
  1026.   opts := SynEdit1.Options;
  1027.   if eoShowSpecialChars in SynEdit1.Options then
  1028.   begin
  1029.     BtnSpecialChars.Picture.Assign(BtnSpecialCharsOff.Picture);
  1030.     Exclude(opts, eoShowSpecialChars);
  1031.     TFastPHPConfig.SpecialChars := false;
  1032.   end
  1033.   else
  1034.   begin
  1035.     BtnSpecialChars.Picture.Assign(BtnSpecialCharsOn.Picture);
  1036.     Include(opts, eoShowSpecialChars);
  1037.     TFastPHPConfig.SpecialChars := true;
  1038.   end;
  1039.   SynEdit1.Options := opts;
  1040. end;
  1041.  
  1042. procedure TForm1.FileModTimerTimer(Sender: TObject);
  1043. resourcestring
  1044.   SChangeConflict = 'The file was changed in a different application BUT IT WAS ALSO MODIFIED HERE! Reload file AND LOSE CHANGES HERE?';
  1045.   SChangeNotify = 'The file was changed in a different application! Reload file?';
  1046. begin
  1047.   FileModTimer.Enabled := false;
  1048.   if FileModLast <> FileAge(GetScrapFile) then
  1049.   begin
  1050.     FileModLast := FileAge(GetScrapFile);
  1051.     if SynEdit1.Modified then
  1052.     begin
  1053.       if MessageDlg(SChangeConflict, mtWarning, mbYesNoCancel, 0) = mrYes then
  1054.       begin
  1055.         SynEdit1.Lines.LoadFromFile(GetScrapFile);
  1056.       end;
  1057.     end
  1058.     else
  1059.     begin
  1060.       if MessageDlg(SChangeNotify, mtConfirmation, mbYesNoCancel, 0) = mrYes then
  1061.       begin
  1062.         SynEdit1.Lines.LoadFromFile(GetScrapFile);
  1063.       end;
  1064.     end;
  1065.   end;
  1066.   FileModTimer.Enabled := true;
  1067. end;
  1068.  
  1069. procedure TForm1.FontSizeTimerTimer(Sender: TObject);
  1070. begin
  1071.   if Memo2.Font.Size <> (SynEdit1.Font.Size-3) then
  1072.     Memo2.Font.Size := (SynEdit1.Font.Size-3);
  1073. end;
  1074.  
  1075. procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
  1076. begin
  1077.   DragAcceptFiles(Handle, False);
  1078.   TFastPHPConfig.FontSize := SynEdit1.Font.Size;
  1079. end;
  1080.  
  1081. procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
  1082. var
  1083.   r: integer;
  1084. resourcestring
  1085.   SWantToSave = 'Do you want to save?';
  1086. begin
  1087.   if SynEdit1.Modified then
  1088.   begin
  1089.     if (ParamStr(1) <> '') or (FSaveAsFilename <> '') then
  1090.     begin
  1091.       r := MessageDlg(SWantToSave, mtConfirmation, mbYesNoCancel, 0);
  1092.       if r = mrCancel then
  1093.       begin
  1094.         CanClose := false;
  1095.         Exit;
  1096.       end
  1097.       else if r = mrYes then
  1098.       begin
  1099.         ActionSave.Execute;
  1100.         CanClose := true;
  1101.       end;
  1102.     end
  1103.     else
  1104.     begin
  1105.       ActionSave.Execute;
  1106.       CanClose := true;
  1107.     end;
  1108.   end;
  1109. end;
  1110.  
  1111. procedure TForm1.FormCreate(Sender: TObject);
  1112. var
  1113.   exeDir: string;
  1114.   sScrapFile: string;
  1115. begin
  1116.   HlpPrevPageIndex := -1;
  1117.   CurSearchTerm := '';
  1118.   sScrapFile := GetScrapFile;
  1119.   Caption := Caption + ' - ' + sScrapFile;
  1120.   Application.Title := Format('%s - FastPHP', [ExtractFileName(sScrapFile)]); // do not translate!
  1121.   SrcRep := TSynEditFindReplace.Create(self);
  1122.   SrcRep.Editor := SynEdit1;
  1123.   SynEdit1.Gutter.Gradient := HighColorWindows;
  1124.  
  1125.   Screen.Cursors[crMouseGutter] := LoadCursor(hInstance, 'MOUSEGUTTER');
  1126.   SynEdit1.Gutter.Cursor := crMouseGutter;
  1127.  
  1128.   exeDir := IncludeTrailingPathDelimiter(ExtractFilePath(ParamStr(0)));
  1129.   if FileExists(exeDir + 'codeexplorer.bmp') then ImageList1.LoadAndSplitImages(exeDir + 'codeexplorer.bmp');
  1130.  
  1131.   FileModLast := FileAge(sScrapFile);
  1132.   FileModTimer.Enabled := True;
  1133. end;
  1134.  
  1135. procedure TForm1.FormDestroy(Sender: TObject);
  1136. begin
  1137.   if Assigned(ChmIndex) then
  1138.   begin
  1139.     FreeAndNil(ChmIndex);
  1140.   end;
  1141.   FreeAndNil(SrcRep);
  1142.  
  1143.   if hMutex <> 0 then CloseHandle(hMutex); // Note: ReleaseMutex does not work as expected!
  1144.  
  1145.   if Assigned(codeExplorer) then
  1146.   begin
  1147.     codeExplorer.Terminate;
  1148.     codeExplorer.WaitFor;
  1149.     FreeAndNil(codeExplorer);
  1150.   end;
  1151. end;
  1152.  
  1153. procedure TForm1.FormShow(Sender: TObject);
  1154. var
  1155.   ScrapFile: string;
  1156.   tmpFontSize: integer;
  1157.   opts: TSynEditorOptions;
  1158. resourcestring
  1159.   SFileAlreadyOpen = 'File "%s" is alrady open!';
  1160. begin
  1161.   if FormShowRanOnce then exit; // If the theme is changed from normal to dark, OnShow will be called another time
  1162.   FormShowRanOnce := true;
  1163.  
  1164.   DoubleBuffered := true;
  1165.  
  1166.   ScrapFile := GetScrapFile;
  1167.   if ScrapFile = '' then
  1168.   begin
  1169.     Application.Terminate; // Close;
  1170.     exit;
  1171.   end;
  1172.  
  1173.   opts := SynEdit1.Options;
  1174.   if TFastPHPConfig.SpecialChars then
  1175.   begin
  1176.     BtnSpecialChars.Picture.Assign(BtnSpecialCharsOn.Picture);
  1177.     Include(opts, eoShowSpecialChars);
  1178.   end
  1179.   else
  1180.   begin
  1181.     BtnSpecialChars.Picture.Assign(BtnSpecialCharsOff.Picture);
  1182.     Exclude(opts, eoShowSpecialChars);
  1183.   end;
  1184.   SynEdit1.Options := opts;
  1185.  
  1186.   PageControl1.ActivePage := PlaintextTabSheet;
  1187.  
  1188.   PageControl2.ActivePage := CodeTabsheet;
  1189.   HelpTabsheet.TabVisible := false;
  1190.  
  1191.   tmpFontSize := TFastPHPConfig.FontSize;
  1192.   if tmpFontSize <> -1 then SynEdit1.Font.Size := tmpFontSize;
  1193.  
  1194.   if FileExists(ScrapFile) then
  1195.   begin
  1196.     if hMutex = 0 then
  1197.     begin
  1198.       hMutex := CreateMutex(nil, True, PChar('FastPHP'+md5(UpperCase(ScrapFile)))); // do not translate
  1199.       if GetLastError = ERROR_ALREADY_EXISTS then
  1200.       begin
  1201.         // TODO: It would be great if the window of that FastPHP instance would switched to foreground
  1202.         ShowMessageFmt(SFileAlreadyOpen, [ScrapFile]);
  1203.         Application.Terminate; // Close;
  1204.         exit;
  1205.       end;
  1206.  
  1207.       SynEdit1.Lines.LoadFromFile(ScrapFile);
  1208.     end;
  1209.   end
  1210.   else
  1211.     SynEdit1.Lines.Clear;
  1212.  
  1213.   SynEdit1.Modified := false; // is required for Delphi 11
  1214.  
  1215.   SynEdit1.SetFocus;
  1216.  
  1217.   StartCodeExplorer;
  1218.  
  1219.   DragAcceptFiles(Handle, True);
  1220.  
  1221.   StartupTimer.Enabled := true;
  1222. end;
  1223.  
  1224. function TForm1.ContainsUnicodeCharsInAnsiFile: boolean;
  1225. var
  1226.   lines: TStringList;
  1227.   ms: TMemoryStream;
  1228. begin
  1229.   if SynEdit1.Lines.Encoding = TEncoding.UTF8 then
  1230.   begin
  1231.     result := false;
  1232.     exit;
  1233.   end;
  1234.  
  1235.   lines := TStringList.Create;
  1236.   ms := TMemoryStream.Create;
  1237.   try
  1238.     SynEdit1.Lines.SaveToStream(ms);
  1239.     ms.Position := 0;
  1240.     lines.LoadFromStream(ms);
  1241.     result := Trim(lines.Text) <> Trim(synedit1.Lines.Text);
  1242.   finally
  1243.     FreeAndNil(ms);
  1244.     FreeAndNil(lines);
  1245.   end;
  1246. end;
  1247.  
  1248. procedure TForm1.SaveToFile(filename: string);
  1249. var
  1250.   ss: TStringStream;
  1251.   ms: TMemoryStream;
  1252.   fs: TFileStream;
  1253.   eolStyle: string;
  1254.   str: string;
  1255.   UpgradeEncodingToUnicode: boolean;
  1256.   DoReloadEncoding: boolean;
  1257. resourcestring
  1258.   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 "?")';
  1259. begin
  1260.   FileModTimer.Enabled := false;
  1261.  
  1262.   if ContainsUnicodeCharsInAnsiFile then
  1263.   begin
  1264.     DoReloadEncoding := true;
  1265.     case MessageBox(Handle, PChar(LNG_UpgradeToUtf8), PChar(Caption), MB_YESNOCANCEL) of
  1266.       ID_YES:
  1267.       begin
  1268.         UpgradeEncodingToUnicode := true;
  1269.       end;
  1270.       ID_NO:
  1271.       begin
  1272.         UpgradeEncodingToUnicode := false;
  1273.       end;
  1274.       ID_CANCEL:
  1275.       begin
  1276.         Abort;
  1277.       end;
  1278.     end;
  1279.   end
  1280.   else
  1281.   begin
  1282.     DoReloadEncoding := false;
  1283.     UpgradeEncodingToUnicode := false;
  1284.   end;
  1285.  
  1286.   ms := TMemoryStream.Create;
  1287.   ss := TStringStream.Create('');
  1288.   fs := TFileStream.Create(filename, fmCreate);
  1289.   try
  1290.     // Save everything in a memory stream and then to a string
  1291.     // in comparison to "str := SynEdit1.Lines.Text;",
  1292.     // This approach should preserve LF / CRLF line endings
  1293.     if UpgradeEncodingToUnicode then
  1294.       SynEdit1.Lines.SaveToStream(ms, TEncoding.UTF8) // upgrade an ANSI file to UTF-8 (e.g. if you include Japanese characters)
  1295.     else
  1296.       SynEdit1.Lines.SaveToStream(ms);
  1297.     if DoReloadEncoding then
  1298.     begin
  1299.       ms.Position := 0;
  1300.       SynEdit1.Lines.LoadFromStream(ms);
  1301.     end;
  1302.     ms.Position := 0;
  1303.     ss.CopyFrom(ms, ms.Size);
  1304.     ss.Position := 0;
  1305.     str := ss.ReadString(ss.Size);
  1306.     ss.Size := 0; // clear string-stream, because we need it later again
  1307.  
  1308.     // Detect current line-endings
  1309.     if Copy(str, 1, 2) = '#!' then
  1310.     begin
  1311.       // Shebang. Use ONLY Linux LF
  1312.       str := StringReplace(str, #13#10, #10, [rfReplaceAll]);
  1313.       eolStyle := #10 // Linux LF
  1314.     end
  1315.     else
  1316.     begin
  1317.       if Pos(#13#10, str) > 0 then
  1318.         eolStyle := #13#10 // Windows CRLF
  1319.       else if Pos(#10, str) > 0 then
  1320.         eolStyle := #10 // Linux LF
  1321.       else
  1322.       begin
  1323.         if DefaultTextLineBreakStyle = tlbsLF then
  1324.           eolStyle := #10 // Linux LF
  1325.         else if DefaultTextLineBreakStyle = tlbsCRLF then
  1326.           eolStyle := #13#10 // Windows CRLF
  1327.         //else if DefaultTextLineBreakStyle = tlbsCR then
  1328.         //  eolStyle := #13 // Old Mac CR
  1329.         else
  1330.           eolStyle := #13#10; // (Should not happen)
  1331.       end;
  1332.     end;
  1333.  
  1334.     // Unitfy line-endings
  1335.     str := StringReplace(str, #13, '', [rfReplaceAll]);
  1336.     str := StringReplace(str, #10, eolStyle, [rfReplaceAll]);
  1337.  
  1338.     // Replace all trailing linebreaks by a single line break
  1339.     // Note: Removing all line breaks is not good, since Linux's "nano" will
  1340.     //       re-add a linebreak at the end of the file
  1341.     str := TrimRight(str) + eolStyle;
  1342.  
  1343.     // Old versions of Delphi/SynEdit write an UTF-8 BOM, which makes problems
  1344.     // e.g. with AJAX handlers (because AJAX reponses must not have a BOM).
  1345.     // So we try to avoid that.
  1346.     // Note that the output is still UTF-8 encoded if the input file was UTF-8 encoded
  1347.     if Copy(str,1,3) = #$EF#$BB#$BF then Delete(str, 1, 3);
  1348.  
  1349.     // Now save to the file
  1350.     ss.WriteString(str);
  1351.     ss.Position := 0;
  1352.     fs.CopyFrom(ss, ss.Size-ss.Position);
  1353.   finally
  1354.     FreeAndNil(ms);
  1355.     FreeAndNil(ss);
  1356.     FreeAndNil(fs);
  1357.   end;
  1358.  
  1359.   FileModLast := FileAge(GetScrapFile);
  1360.   FileModTimer.Enabled := True;
  1361. end;
  1362.  
  1363. procedure TForm1.StartCodeExplorer;
  1364. begin
  1365.   codeExplorer := TRunCodeExplorer.Create(true);
  1366.   codeExplorer.InputRequestCallback := InputRequestCallback;
  1367.   codeExplorer.OutputNotifyCallback := OutputNotifyCallback;
  1368.   codeExplorer.PhpExe := GetPHPExe;
  1369.   codeExplorer.PhpFile := IncludeTrailingPathDelimiter(ExtractFileDir(Application.ExeName)) + 'codeexplorer.php'; // GetScrapFile;
  1370.   codeExplorer.WorkDir := ExtractFileDir(Application.ExeName);
  1371.   codeExplorer.Resume;
  1372. end;
  1373.  
  1374. procedure TForm1.StartUpTimerTimer(Sender: TObject);
  1375. begin
  1376.   StartupTimer.Enabled := false;
  1377.  
  1378.   // We need this timer because we cannot change the Theme during OnShow,
  1379.   // because the Delphi VCL Theme is buggy!
  1380.  
  1381.   if TFastPHPConfig.DarkTheme then
  1382.   begin
  1383.     BtnLight.Picture.Assign(BtnLightOff.Picture);
  1384.     Theme_Dark;
  1385.   end
  1386.   else
  1387.   begin
  1388.     BtnLight.Picture.Assign(BtnLightOn.Picture);
  1389.     Theme_Light;
  1390.   end;
  1391. end;
  1392.  
  1393. function TForm1.GetScrapFile: string;
  1394. var
  1395.   tmpPath: string;
  1396. resourcestring
  1397.   SFileDoesNotExistsCreate = 'File %s does not exist. Create it?';
  1398.   SPathDoesNotExistTryAgain = 'Path does not exist! Please try again.';
  1399. begin
  1400.   if FSaveAsFilename <> '' then
  1401.   begin
  1402.     result := FSaveAsFilename;
  1403.     exit;
  1404.   end;
  1405.  
  1406.   if FScrapFile <> '' then
  1407.   begin
  1408.     result := FScrapFile;
  1409.     exit;
  1410.   end;
  1411.  
  1412.   if ParamStr(1) <> '' then
  1413.   begin
  1414.     // Program was started with a filename
  1415.  
  1416.     result := ParamStr(1);
  1417.  
  1418.     if not FileExists(result) then
  1419.     begin
  1420.       case MessageDlg(Format(SFileDoesNotExistsCreate, [result]), mtConfirmation, mbYesNoCancel, 0) of
  1421.         mrYes:
  1422.           try
  1423.             SaveToFile(result);
  1424.           except
  1425.             on E: Exception do
  1426.             begin
  1427.               MessageDlg(E.Message, mtError, [mbOk], 0);
  1428.               Application.Terminate;
  1429.               result := '';
  1430.               exit;
  1431.             end;
  1432.           end;
  1433.         mrNo:
  1434.           begin
  1435.             Application.Terminate;
  1436.             result := '';
  1437.             exit;
  1438.           end;
  1439.         mrCancel:
  1440.           begin
  1441.             Application.Terminate;
  1442.             result := '';
  1443.             exit;
  1444.           end;
  1445.       end;
  1446.     end;
  1447.   end
  1448.   else
  1449.   begin
  1450.     // Program is started without filename -> use scrap file
  1451.  
  1452.     result := TFastPHPConfig.ScrapFile;
  1453.  
  1454.     if not FileExists(result) then
  1455.     begin
  1456.       repeat
  1457.         {$REGION 'Determinate opendialog initial directory'}
  1458.         if result <> '' then
  1459.         begin
  1460.           tmpPath := ExtractFilePath(result);
  1461.           if DirectoryExists(tmpPath) then
  1462.           begin
  1463.             OpenDialog3.InitialDir := tmpPath;
  1464.             OpenDialog3.FileName := Result;
  1465.           end
  1466.           else
  1467.           begin
  1468.             OpenDialog3.InitialDir := GetMyDocumentsFolder;
  1469.           end;
  1470.         end
  1471.         else
  1472.         begin
  1473.           OpenDialog3.InitialDir := GetMyDocumentsFolder;
  1474.         end;
  1475.         {$ENDREGION}
  1476.  
  1477.         if not OpenDialog3.Execute then
  1478.         begin
  1479.           Application.Terminate;
  1480.           result := '';
  1481.           exit;
  1482.         end;
  1483.  
  1484.         if not DirectoryExists(ExtractFilePath(OpenDialog3.FileName)) then
  1485.         begin
  1486.           MessageDlg(SPathDoesNotExistTryAgain, mtWarning, [mbOk], 0);
  1487.         end
  1488.         else
  1489.         begin
  1490.           result := OpenDialog3.FileName;
  1491.         end;
  1492.       until result <> '';
  1493.  
  1494.       if not FileExists(result) then
  1495.       begin
  1496.         try
  1497.           // Try saving the file; check if we have permissions
  1498.           //SynEdit1.Lines.Clear;
  1499.           SaveToFile(result);
  1500.         except
  1501.           on E: Exception do
  1502.           begin
  1503.             MessageDlg(E.Message, mtError, [mbOk], 0);
  1504.             Application.Terminate;
  1505.             result := '';
  1506.             exit;
  1507.           end;
  1508.         end;
  1509.       end;
  1510.  
  1511.       TFastPHPConfig.ScrapFile := result;
  1512.       FScrapFile := result;
  1513.     end;
  1514.   end;
  1515. end;
  1516.  
  1517. procedure TForm1.Help;
  1518. var
  1519.   IndexFile, chmFile, w, OriginalWord, url: string;
  1520.   internalHtmlFile: string;
  1521. resourcestring
  1522.   SChmFileNotAValidPHPDocumentation = 'The CHM file is not a valid PHP documentation. Cannot use help.';
  1523.   SUnknownErrorCannotUseHelp = 'Unknown error. Cannot use help.';
  1524.   SNoHelpAvailable = 'No help for "%s" available';
  1525. begin
  1526.   if not Assigned(ChmIndex) then
  1527.   begin
  1528.     IndexFile := TFastPHPConfig.HelpIndex;
  1529.     IndexFile := ChangeFileExt(IndexFile, '.ini'); // Just to be sure. Maybe someone wrote manually the ".chm" file in there
  1530.     if FileExists(IndexFile) then
  1531.     begin
  1532.       ChmIndex := TMemIniFile.Create(IndexFile);
  1533.     end;
  1534.   end;
  1535.  
  1536.   if Assigned(ChmIndex) then
  1537.   begin
  1538.     IndexFile := TFastPHPConfig.HelpIndex;
  1539.     // We don't check if IndexFile still exists. It is not important since we have ChmIndex pre-loaded in memory
  1540.  
  1541.     chmFile := ChangeFileExt(IndexFile, '.chm');
  1542.     if not FileExists(chmFile) then
  1543.     begin
  1544.       FreeAndNil(ChmIndex);
  1545.     end;
  1546.   end;
  1547.  
  1548.   if not Assigned(ChmIndex) then
  1549.   begin
  1550.     if not OpenDialog1.Execute then exit;
  1551.  
  1552.     chmFile := OpenDialog1.FileName;
  1553.     if not FileExists(chmFile) then exit;
  1554.  
  1555.     IndexFile := ChangeFileExt(chmFile, '.ini');
  1556.  
  1557.     if not FileExists(IndexFile) then
  1558.     begin
  1559.       Panel1.Align := alClient;
  1560.       Panel1.Visible := true;
  1561.       Panel1.BringToFront;
  1562.       Screen.Cursor := crHourGlass;
  1563.       Application.ProcessMessages;
  1564.       try
  1565.         if not ParseCHM(chmFile) then
  1566.         begin
  1567.           MessageDlg(SChmFileNotAValidPHPDocumentation, mtError, [mbOk], 0);
  1568.           exit;
  1569.         end;
  1570.       finally
  1571.         Screen.Cursor := crDefault;
  1572.         Panel1.Visible := false;
  1573.       end;
  1574.  
  1575.       if not FileExists(IndexFile) then
  1576.       begin
  1577.         MessageDlg(SUnknownErrorCannotUseHelp, mtError, [mbOk], 0);
  1578.         exit;
  1579.       end;
  1580.     end;
  1581.  
  1582.     TFastPHPConfig.HelpIndex := IndexFile;
  1583.  
  1584.     ChmIndex := TMemIniFile.Create(IndexFile);
  1585.   end;
  1586.  
  1587.   w := GetWordUnderCaret(SynEdit1);
  1588.   if w = '' then exit;
  1589.   {$IFDEF UNICODE}
  1590.   if CharInSet(w[1], ['0'..'9']) then exit;
  1591.   {$ELSE}
  1592.   if w[1] in ['0'..'9'] then exit;
  1593.   {$ENDIF}
  1594.  
  1595.   Originalword := w;
  1596. //  w := StringReplace(w, '_', '-', [rfReplaceAll]);
  1597.   w := LowerCase(w);
  1598.   CurSearchTerm := w;
  1599.  
  1600.   internalHtmlFile := ChmIndex.ReadString('function', CurSearchTerm, ''); // do not translate
  1601.   if internalHtmlFile = '' then
  1602.     internalHtmlFile := ChmIndex.ReadString('_HelpWords_', CurSearchTerm, ''); // do not translate
  1603.   if internalHtmlFile = '' then
  1604.   begin
  1605.     HelpTabsheet.TabVisible := false;
  1606.     HlpPrevPageIndex := -1;
  1607.     ShowMessageFmt(SNoHelpAvailable, [Originalword]);
  1608.     Exit;
  1609.   end;
  1610.  
  1611.   url := 'mk:@MSITStore:'+ChmFile+'::'+internalHtmlFile;
  1612.  
  1613.   HlpPrevPageIndex := PageControl2.ActivePageIndex; // Return by pressing ESC
  1614.   HelpTabsheet.TabVisible := true;
  1615.   PageControl2.ActivePage := HelpTabsheet;
  1616.   WebBrowser2.Navigate(url);
  1617.   WebBrowser2.Wait;
  1618. end;
  1619.  
  1620. procedure TForm1.GotoLineNo(LineNo:integer);
  1621. var
  1622.   line: string;
  1623.   i: integer;
  1624. begin
  1625.   SynEdit1.GotoLineAndCenter(LineNo);
  1626.  
  1627.   // Skip indent
  1628.   line := SynEdit1.Lines[SynEdit1.CaretY];
  1629.   for i := 1 to Length(line) do
  1630.   begin
  1631.     {$IFDEF UNICODE}
  1632.     if not CharInSet(line[i], [' ', #9]) then
  1633.     {$ELSE}
  1634.     if not (line[i] in [' ', #9]) then
  1635.     {$ENDIF}
  1636.     begin
  1637.       SynEdit1.CaretX := i-1;
  1638.       break;
  1639.     end;
  1640.   end;
  1641.  
  1642.   PageControl2.ActivePage := CodeTabsheet;
  1643.   if SynEdit1.CanFocus then SynEdit1.SetFocus;
  1644. end;
  1645.  
  1646. procedure TForm1.PageControl2Changing(Sender: TObject;
  1647.   var AllowChange: Boolean);
  1648. begin
  1649.   if PageControl2.ActivePage = HelpTabsheet then
  1650.     HlpPrevPageIndex := -1
  1651.   else
  1652.     HlpPrevPageIndex := PageControl2.ActivePageIndex;
  1653.  
  1654.   AllowChange := true;
  1655. end;
  1656.  
  1657. procedure TForm1.Memo2DblClick(Sender: TObject);
  1658. var
  1659.   line: string;
  1660.  
  1661.   procedure _process(toFind: string);
  1662.   var
  1663.     p, lineno: integer;
  1664.   begin
  1665.     if FileSystemCaseSensitive then
  1666.       p := Pos(toFind, line)
  1667.     else
  1668.       p := Pos(LowerCase(toFind), LowerCase(line));
  1669.     if p <> 0 then
  1670.     begin
  1671.       line := copy(line, p+length(toFind), 99); // TODO: "(test.php:123)" does not work, because "123)" will be tried to convert to Integer
  1672.       if not TryStrToInt(line, lineno) then exit;
  1673.       GotoLineNo(lineno);
  1674.     end;
  1675.   end;
  1676.  
  1677. begin
  1678.   line := memo2.Lines.Strings[Memo2.CaretPos.Y];
  1679.  
  1680.   {$REGION 'Possibility 1: filename.php:lineno'}
  1681.   _process(ExtractFileName(GetScrapFile) + ':');
  1682.   {$ENDREGION}
  1683.  
  1684.   {$REGION 'Possibility 2: on line xx'}
  1685.   _process(ExtractFileName(GetScrapFile) + ' on line '); // do not translate!
  1686.   {$ENDREGION}
  1687. end;
  1688.  
  1689. procedure TForm1.Memo2KeyDown(Sender: TObject; var Key: Word;
  1690.   Shift: TShiftState);
  1691. begin
  1692.   if ((ssCtrl in Shift) and (Key = 65)) then TMemo(Sender).SelectAll;
  1693. end;
  1694.  
  1695. function TForm1.MarkUpLineReference(cont: string): string;
  1696.  
  1697.   procedure _process(toFind: string);
  1698.   var
  1699.     p, a, b: integer;
  1700.     num: integer;
  1701.     insert_a, insert_b: string;
  1702.   begin
  1703.     if FileSystemCaseSensitive then
  1704.       p := Pos(toFind, cont)
  1705.     else
  1706.       p := Pos(LowerCase(toFind), LowerCase(cont));
  1707.     while p >= 1 do
  1708.     begin
  1709.       a := p;
  1710.       b := p + length(toFind);
  1711.       num := 0;
  1712.       {$IFDEF UNICODE}
  1713.       while CharInSet(cont[b], ['0'..'9']) do
  1714.       {$ELSE}
  1715.       while cont[b] in ['0'..'9'] do
  1716.       {$ENDIF}
  1717.       begin
  1718.         num := num*10 + StrToInt(cont[b]);
  1719.         inc(b);
  1720.       end;
  1721.  
  1722.       insert_b := '</a>';
  1723.       insert_a := '<a href="' + FASTPHP_GOTO_URI_PREFIX + IntToStr(num) + '">';
  1724.  
  1725.       insert(insert_b, cont, b);
  1726.       insert(insert_a, cont, a);
  1727.  
  1728.       p := b + Length(insert_a) + Length(insert_b);
  1729.  
  1730.       p := PosEx(toFind, cont, p+1);
  1731.     end;
  1732.   end;
  1733.  
  1734. begin
  1735.   {$REGION 'Possibility 1: filename.php:lineno'}
  1736.   _process(ExtractFileName(GetScrapFile) + ':');
  1737.   {$ENDREGION}
  1738.  
  1739.   {$REGION 'Possibility 2: on line xx'}
  1740.   _process(ExtractFileName(GetScrapFile) + ' on line '); // do not translate!
  1741.   {$ENDREGION}
  1742.  
  1743.   result := cont;
  1744. end;
  1745.  
  1746. function TForm1.InputRequestCallback(var data: AnsiString): boolean;
  1747. begin
  1748.   data := UTF8Encode(SynEdit1.Text);
  1749.   result := true;
  1750. end;
  1751.  
  1752. function TForm1.IsThemeDark: boolean;
  1753. begin
  1754.   result := Assigned(TStyleManager.ActiveStyle) and (TStyleManager.ActiveStyle.Name<>'Windows');
  1755. end;
  1756.  
  1757. function TForm1.OutputNotifyCallback(const data: AnsiString): boolean;
  1758. begin
  1759.   result := TreeView1.FillWithFastPHPData(string(data));
  1760. end;
  1761.  
  1762. end.
  1763.