Subversion Repositories fastphp

Rev

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