Subversion Repositories fastphp

Rev

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