Subversion Repositories fastphp

Rev

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