Subversion Repositories fastphp

Rev

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