Subversion Repositories fastphp

Rev

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