Subversion Repositories fastphp

Rev

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