Subversion Repositories fastphp

Rev

Rev 78 | Rev 80 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

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