Subversion Repositories fastphp

Rev

Rev 93 | Rev 96 | 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. // - Is it possible that UTF8 BOM automatically gets removed by FastPHP, generating pure ANSI?
  29.  
  30. // Note in re Unicode:
  31. // - In Embarcadero® Delphi 10.4 Version 27.0.40680.4203:
  32. //   SynEdit correctly detects UTF-8 files without BOM as well as ANSI files with Umlauts.
  33. //   (Previous versions could not detect UTF-8 files without BOM?!)
  34. // - If BOM is existing, it will be removed. (which is good, because this is defined by PSR-1)
  35.  
  36. // Small things:
  37. // - The scroll bars of SynEdit are not affected by the dark theme
  38.  
  39. // Future ideas
  40. // - code insight
  41. // - verschiedene php versionen?
  42. // - webbrowser1 nur laden, wenn man den tab anw�hlt?
  43. // - doppelklick auf tab soll diesen schlie�en
  44. // - Onlinehelp (www) aufrufen oder CHM datei
  45. // - Let all colors be adjustable
  46. // - code in bildschirmmitte (horizontal)?
  47. // - search in files of a directory
  48. // - Files in multiple tabs?
  49. // - Configurable tabulator display-width
  50.  
  51. interface
  52.  
  53. uses
  54.   // TODO: "{$IFDEF USE_SHDOCVW_TLB}_TLB{$ENDIF}" does not work with Delphi 10.2
  55.   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  56.   Dialogs, StdCtrls, OleCtrls, ComCtrls, ExtCtrls, ToolWin, IniFiles,
  57.   SynEditHighlighter, SynHighlighterPHP, SynEdit, ShDocVw, FindReplace,
  58.   ActnList, SynEditMiscClasses, SynEditSearch, RunPHP, ImgList, SynUnicode,
  59.   System.ImageList, System.Actions, Vcl.Menus, Vcl.Themes, System.UITypes,
  60.   SynEditCodeFolding;
  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.       newval: string;
  445.       somethingchanged: boolean;
  446.     begin
  447.       somethingchanged := false;
  448.       for i := 0 to lines.Count-1 do
  449.       begin
  450.         spaces := SpacesAtBeginning(lines.Strings[i]);
  451.         newval := StringOfChar(#9, spaces div indent) + StringOfChar(' ', spaces mod indent) + Copy(lines.Strings[i], spaces+1, Length(lines.Strings[i])-spaces);
  452.         if lines.Strings[i] <> newval then
  453.         begin
  454.           somethingchanged := true;
  455.           lines.Strings[i] := newval;
  456.         end;
  457.       end;
  458.       if somethingchanged then
  459.       begin
  460.         SynEdit1Change(SynEdit1); // set the "changed" flag
  461.       end;
  462.     end;
  463.  
  464.     function SpacesAvailable(lines: {$IFDEF UNICODE}TStrings{$ELSE}TUnicodeStrings{$ENDIF}): boolean;
  465.     var
  466.       i, spaces: integer;
  467.     begin
  468.       for i := 0 to lines.Count-1 do
  469.       begin
  470.         spaces := SpacesAtBeginning(lines.Strings[i]);
  471.         if spaces > 0 then
  472.         begin
  473.           result := true;
  474.           exit;
  475.         end;
  476.       end;
  477.       result := false;
  478.       exit;
  479.     end;
  480.  
  481. var
  482.   val: string;
  483.   ind: integer;
  484. resourcestring
  485.   SNoLinesAvailable = 'No lines with spaces at the beginning available';
  486.   SSpacesToLabs = 'Spaces to tabs';
  487.   SIndent = 'Indent:';
  488. begin
  489.   // TODO: if something is selected, only process the selected part
  490.  
  491.   if not SpacesAvailable(SynEdit1.Lines) then
  492.   begin
  493.     MessageDlg(SNoLinesAvailable, mtInformation, [mbOk], 0);
  494.     exit;
  495.   end;
  496.  
  497.   ind := GuessIndent(SynEdit1.Lines);
  498.   if ind <> -1 then val := IntToStr(ind);
  499.  
  500.   if not InputQuery(SSpacesToLabs, SIndent, val) or
  501.      not TryStrToInt(Trim(val), ind) then
  502.   begin
  503.     if SynEdit1.CanFocus then SynEdit1.SetFocus;
  504.     exit;
  505.   end;
  506.  
  507.   if ind = 0 then exit;
  508.   SpaceToTab(SynEdit1.Lines, ind);
  509. end;
  510.  
  511. procedure TForm1.ActionESCExecute(Sender: TObject);
  512. begin
  513.   if (HlpPrevPageIndex <> -1) and (PageControl2.ActivePage = HelpTabSheet) and
  514.      (HelpTabsheet.TabVisible) then
  515.   begin
  516.     PageControl2.ActivePageIndex := HlpPrevPageIndex;
  517.     HelpTabsheet.TabVisible := false;
  518.   end;
  519.  
  520.   // Dirty hack...
  521.   SrcRep.CloseDialogs;
  522. end;
  523.  
  524. procedure TForm1.ActionFindExecute(Sender: TObject);
  525. begin
  526.   SrcRep.FindExecute;
  527. end;
  528.  
  529. procedure TForm1.Run(Sender: TObject);
  530. var
  531.   bakTS: TTabSheet;
  532.   //ss: TStringStream;
  533.   //bakPos: Int64;
  534. begin
  535.   memo2.Lines.Text := '';
  536.  
  537.   if not BrowserLoadedOnce then
  538.   begin
  539.     bakTS := PageControl1.ActivePage;
  540.     try
  541.       PageControl1.ActivePage := HtmlTabSheet; // Required for the first time, otherwise, WebBrowser1.Clear will hang
  542.       Webbrowser1.Clear;
  543.     finally
  544.       PageControl1.ActivePage := bakTS;
  545.     end;
  546.     BrowserLoadedOnce := true;
  547.   end
  548.   else
  549.     Webbrowser1.Clear;
  550.  
  551.   Screen.Cursor := crHourGlass; // TODO: Doesn't work with F9
  552.   Application.ProcessMessages;
  553.  
  554.   try
  555.     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.
  556.  
  557.     // TODO 70421 * <fastphp> implement flush() with ContentCallBack implementieren... For long running scripts I want to see status changes via javascript which are loaded step by step
  558.     // TODO 70422 * <fastphp> when a script has an endless loop, i want to have a possibility to cancel it
  559.     if SynEdit1.Lines.Encoding = TEncoding.UTF8 then
  560.       memo2.Lines.Text := Utf8Decode(RunPHPScript(GetScrapFile, Sender=ActionLint, False)) // if we have a UTF-8 file, then the DOS output is double-UTF8 encoded
  561.     else
  562.       memo2.Lines.Text := RunPHPScript(GetScrapFile, Sender=ActionLint, False);
  563.  
  564.     {$REGION 'Show in Web Browser'}
  565.     if SynEdit1.Lines.Encoding = TEncoding.UTF8 then
  566.       Webbrowser1.LoadHTML(MarkUpLineReference('<meta charset="utf-8">'+memo2.Lines.Text), GetScrapFile)
  567.     else
  568.       Webbrowser1.LoadHTML(MarkUpLineReference(memo2.Lines.Text), GetScrapFile);
  569.  
  570.     // Alternatively:
  571.     (*
  572.     ss := TstringStream.Create;
  573.     ss.WriteString(MarkUpLineReference(memo2.Lines.Text));
  574.     ss.Position := 0;
  575.     Webbrowser1.LoadStream(ss, GetScrapFile);
  576.     Webbrowser1.Wait;
  577.     ss.Free;
  578.     *)
  579.     {$ENDREGION}
  580.  
  581.     if IsTextHTML(memo2.lines.text) then
  582.       PageControl1.ActivePage := HtmlTabSheet
  583.     else
  584.       PageControl1.ActivePage := PlaintextTabSheet;
  585.   finally
  586.     Screen.Cursor := crDefault;
  587.   end;
  588. end;
  589.  
  590. procedure TForm1.RunConsole(Sender: TObject);
  591. begin
  592.   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.
  593.   RunPHPScript(GetScrapFile, Sender=ActionLint, True);
  594. end;
  595.  
  596. procedure TForm1.SynEdit1Change(Sender: TObject);
  597. begin
  598.   RefreshModifySign;
  599. end;
  600.  
  601. procedure TForm1.SynEdit1DropFiles(Sender: TObject; X, Y: Integer;
  602.   AFiles: TStrings);
  603. var
  604.   FileName: string;
  605. const
  606.   WARN_FILE_COUNT = 10;
  607. resourcestring
  608.   SAreYouSure = 'Are you sure you want to open %d files?';
  609. begin
  610.   if AFiles.Count > WARN_FILE_COUNT then
  611.   begin
  612.     if not MessageDlg(Format(SAreYouSure, [WARN_FILE_COUNT]), mtConfirmation, mbYesNoCancel, 0) <> mrYes then
  613.       exit;
  614.   end;
  615.  
  616.   for FileName in AFiles do
  617.   begin
  618.     ShellExecute(0, 'open', PChar(ParamStr(0)), PChar('"' + FileName + '"'), '', SW_NORMAL);
  619.   end;
  620. end;
  621.  
  622. procedure TForm1.SynEdit1GutterClick(Sender: TObject; Button: TMouseButton; X,
  623.   Y, Line: Integer; Mark: TSynEditMark);
  624. begin
  625.   (*
  626.   TSynEdit(Sender).CaretX := 1;
  627.   TSynEdit(Sender).CaretY := Line;
  628.   TSynEdit(Sender).SelLength := Length(TSynEdit(Sender).LineText);
  629.   *)
  630. end;
  631.  
  632. procedure TForm1.SynEdit1MouseCursor(Sender: TObject; const aLineCharPos: TBufferCoord; var aCursor: TCursor);
  633. {$IFDEF OnlineHelp}
  634. var
  635.   Line: Integer;
  636.   Column: Integer;
  637.   word: string;
  638. begin
  639.   Line  := aLineCharPos.Line-1;
  640.   Column := aLineCharPos.Char-1;
  641.   word := GetWordUnderPos(TSynEdit(Sender), Line, Column);
  642.   if word <> gOnlineHelpWord then
  643.   begin
  644.     gOnlineHelpWord := word;
  645.     Timer1.Enabled := false;
  646.     Timer1.Enabled := true;
  647.   end;
  648. {$ELSE}
  649. begin
  650. {$ENDIF}
  651. end;
  652.  
  653. procedure TForm1.SynEdit1MouseWheelDown(Sender: TObject; Shift: TShiftState;
  654.   MousePos: TPoint; var Handled: Boolean);
  655. begin
  656.   if ssCtrl in Shift then
  657.   begin
  658.     SynEdit1.Font.Size := Max(SynEdit1.Font.Size - 1, 5);
  659.     Handled := true;
  660.   end
  661.   else Handled := false;
  662. end;
  663.  
  664. procedure TForm1.SynEdit1MouseWheelUp(Sender: TObject; Shift: TShiftState;
  665.   MousePos: TPoint; var Handled: Boolean);
  666. begin
  667.   if ssCtrl in Shift then
  668.   begin
  669.     SynEdit1.Font.Size := SynEdit1.Font.Size + 1;
  670.     Handled := true;
  671.   end
  672.   else Handled := false;
  673. end;
  674.  
  675. procedure TForm1.SynEdit1PaintTransient(Sender: TObject; Canvas: TCanvas; TransientType: TTransientType);
  676. var
  677.   Editor: TSynEdit;
  678.   OpenChars: array of WideChar;//[0..2] of WideChar=();
  679.   CloseChars: array of WideChar;//[0..2] of WideChar=();
  680.  
  681.   function IsCharBracket(AChar: WideChar): Boolean;
  682.   begin
  683.     case AChar of
  684.       '{','[','(','<','}',']',')','>':
  685.         Result := True;
  686.       else
  687.         Result := False;
  688.     end;
  689.   end;
  690.  
  691.   function CharToPixels(P: TBufferCoord): TPoint;
  692.   begin
  693.     Result := Editor.RowColumnToPixels(Editor.BufferToDisplayPos(P));
  694.   end;
  695.  
  696. var
  697.   COLOR_FG: TColor;
  698.   COLOR_BG: TColor;
  699.   P: TBufferCoord;
  700.   Pix: TPoint;
  701.   D: TDisplayCoord;
  702.   S: UnicodeString;
  703.   I: Integer;
  704.   Attri: TSynHighlighterAttributes;
  705.   ArrayLength: Integer;
  706.   start: Integer;
  707.   TmpCharA, TmpCharB: WideChar;
  708. begin
  709.   // Source: https://github.com/SynEdit/SynEdit/blob/master/Demos/OnPaintTransientDemo/Unit1.pas
  710.  
  711.   if IsThemeDark then
  712.   begin
  713.     COLOR_FG := clLime;
  714.     COLOR_BG := clGreen;
  715.   end
  716.   else
  717.   begin
  718.     COLOR_FG := clGreen;
  719.     COLOR_BG := clLime;
  720.   end;
  721.  
  722.   if TSynEdit(Sender).SelAvail then exit;
  723.   Editor := TSynEdit(Sender);
  724.   ArrayLength:= 3;
  725.  
  726.   (*
  727.   if (Editor.Highlighter = shHTML) or (Editor.Highlighter = shXML) then
  728.     inc(ArrayLength);
  729.   *)
  730.  
  731.   SetLength(OpenChars, ArrayLength);
  732.   SetLength(CloseChars, ArrayLength);
  733.   for i := 0 to ArrayLength - 1 do
  734.   begin
  735.     case i of
  736.       0: begin OpenChars[i] := '('; CloseChars[i] := ')'; end;
  737.       1: begin OpenChars[i] := '{'; CloseChars[i] := '}'; end;
  738.       2: begin OpenChars[i] := '['; CloseChars[i] := ']'; end;
  739.       3: begin OpenChars[i] := '<'; CloseChars[i] := '>'; end;
  740.     end;
  741.   end;
  742.  
  743.   P := Editor.CaretXY;
  744.   D := Editor.DisplayXY;
  745.  
  746.   Start := Editor.SelStart;
  747.  
  748.   if (Start > 0) and (Start <= length(Editor.Text)) then
  749.     TmpCharA := Editor.Text[Start]
  750.   else
  751.     TmpCharA := #0;
  752.  
  753.   if (Start > 0){Added by VTS} and (Start < length(Editor.Text)) then
  754.     TmpCharB := Editor.Text[Start + 1]
  755.   else
  756.     TmpCharB := #0;
  757.  
  758.   if not IsCharBracket(TmpCharA) and not IsCharBracket(TmpCharB) then exit;
  759.   S := TmpCharB;
  760.   if not IsCharBracket(TmpCharB) then
  761.   begin
  762.     P.Char := P.Char - 1;
  763.     S := TmpCharA;
  764.   end;
  765.   Editor.GetHighlighterAttriAtRowCol(P, S, Attri);
  766.  
  767.   if (Editor.Highlighter.SymbolAttribute = Attri) then
  768.   begin
  769.     for i := low(OpenChars) to High(OpenChars) do
  770.     begin
  771.       if (S = OpenChars[i]) or (S = CloseChars[i]) then
  772.       begin
  773.         Pix := CharToPixels(P);
  774.  
  775.         Editor.Canvas.Brush.Style := bsSolid;//Clear;
  776.         Editor.Canvas.Font.Assign(Editor.Font);
  777.         Editor.Canvas.Font.Style := Attri.Style;
  778.  
  779.         if (TransientType = ttAfter) then
  780.         begin
  781.           Editor.Canvas.Font.Color := COLOR_FG;
  782.           Editor.Canvas.Brush.Color := COLOR_BG;
  783.         end
  784.         else
  785.         begin
  786.           Editor.Canvas.Font.Color := Attri.Foreground;
  787.           Editor.Canvas.Brush.Color := Attri.Background;
  788.         end;
  789.         if Editor.Canvas.Font.Color = clNone then
  790.           Editor.Canvas.Font.Color := Editor.Font.Color;
  791.         if Editor.Canvas.Brush.Color = clNone then
  792.           Editor.Canvas.Brush.Color := Editor.Color;
  793.  
  794.         Editor.Canvas.TextOut(Pix.X, Pix.Y, S);
  795.         P := Editor.GetMatchingBracketEx(P);
  796.  
  797.         if (P.Char > 0) and (P.Line > 0) then
  798.         begin
  799.           Pix := CharToPixels(P);
  800.           if Pix.X > Editor.Gutter.Width then
  801.           begin
  802.             {$REGION 'Added by ViaThinkSoft'}
  803.             if (TransientType = ttAfter) then
  804.             begin
  805.               Editor.Canvas.Font.Color := COLOR_FG;
  806.               Editor.Canvas.Brush.Color := COLOR_BG;
  807.             end
  808.             else
  809.             begin
  810.               Editor.Canvas.Font.Color := Attri.Foreground;
  811.               Editor.Canvas.Brush.Color := Attri.Background;
  812.             end;
  813.             if Editor.Canvas.Font.Color = clNone then
  814.               Editor.Canvas.Font.Color := Editor.Font.Color;
  815.             if Editor.Canvas.Brush.Color = clNone then
  816.               Editor.Canvas.Brush.Color := Editor.Color;
  817.             {$ENDREGION}
  818.             if S = OpenChars[i] then
  819.               Editor.Canvas.TextOut(Pix.X, Pix.Y, CloseChars[i])
  820.             else Editor.Canvas.TextOut(Pix.X, Pix.Y, OpenChars[i]);
  821.           end;
  822.         end;
  823.       end;
  824.     end;
  825.     Editor.Canvas.Brush.Style := bsSolid;
  826.   end;
  827. end;
  828.  
  829. procedure TForm1.SynEditFocusTimerTimer(Sender: TObject);
  830. begin
  831.   SynEditFocusTimer.Enabled := false;
  832.   Button1.SetFocus; // Workaround for weird bug... This (and the timer) is necessary to get the focus to SynEdit1
  833.   SynEdit1.SetFocus;
  834. end;
  835.  
  836. procedure TForm1.Theme_Dark;
  837. begin
  838.   if IsThemeDark then exit;
  839.   TStyleManager.TrySetStyle('Windows10 SlateGray'); // do not translate
  840.   Color := 1316887;
  841.   Font.Color := clCream;
  842.   //Memo2.Font.Color := clCream;
  843.   //Memo2.ParentColor := true;
  844.   SynEdit1.ActiveLineColor := 2238502;
  845.   SynEdit1.Color := 1316887;
  846.   SynEdit1.Font.Color := clCream;
  847.   SynEdit1.Gutter.Color := 1316887;
  848.   SynEdit1.Gutter.Font.Color := clCream;
  849.   SynEdit1.Gutter.GradientStartColor := 2238502;
  850.   SynEdit1.Gutter.GradientEndColor := 1316887;
  851.   SynPHPSyn1.CommentAttri.Foreground := $00837B82;
  852.   SynPHPSyn1.IdentifierAttri.Foreground := 9627120;
  853.   SynPHPSyn1.KeyAttri.Foreground := 4157595;
  854.   SynPHPSyn1.NumberAttri.Foreground := 5008079;
  855.   SynPHPSyn1.StringAttri.Foreground := 6987151;
  856.   SynPHPSyn1.SymbolAttri.Foreground := 8769754;
  857.   SynPHPSyn1.VariableAttri.Foreground := 6924493;
  858. end;
  859.  
  860. procedure TForm1.Theme_Light;
  861. begin
  862.   if not IsThemeDark then exit;
  863.   TStyleManager.TrySetStyle('Windows'); // do not translate
  864.   Color := clBtnFace;
  865.   Font.Color := clWindowText;
  866.   //Memo2.Font.Color := clWindowText;
  867.   SynEdit1.ActiveLineColor := 14680010;
  868.   SynEdit1.Color := clCream;
  869.   SynEdit1.Font.Color := clWindowText;
  870.   SynEdit1.Gutter.Color := clBtnFace;
  871.   SynEdit1.Gutter.Font.Color := clWindowText;
  872.   SynEdit1.Gutter.GradientStartcolor := cl3dLight;
  873.   SynEdit1.Gutter.GradientEndColor := clBtnFace;;
  874.   SynPHPSyn1.CommentAttri.Foreground := 33023;
  875.   SynPHPSyn1.IdentifierAttri.Foreground := 4194304;
  876.   SynPHPSyn1.KeyAttri.Foreground := 4227072;
  877.   SynPHPSyn1.NumberAttri.Foreground := 213;
  878.   SynPHPSyn1.StringAttri.Foreground := 13762560;
  879.   SynPHPSyn1.SymbolAttri.Foreground := 4227072;
  880.   SynPHPSyn1.VariableAttri.Foreground := 213;
  881. end;
  882.  
  883. procedure TForm1.Timer1Timer(Sender: TObject);
  884. begin
  885.   {$IFDEF OnlineHelp}
  886.   Timer1.Enabled := false;
  887.  
  888.   // TODO: Insert a small online help hint
  889.   //Caption := gOnlineHelpWord;
  890.   {$ENDIF}
  891. end;
  892.  
  893. procedure TForm1.TreeView1DblClick(Sender: TObject);
  894. var
  895.   tn: TTreeNode;
  896.   lineNo: integer;
  897. begin
  898.   tn := TTreeView(Sender).Selected;
  899.   if tn = nil then exit;
  900.   lineNo := Integer(tn.Data);
  901.   if lineNo > 0 then GotoLineNo(lineNo);
  902. end;
  903.  
  904. (*
  905. {$IFDEF USE_SHDOCVW_TLB}
  906. *)
  907. procedure TForm1.WebBrowser1BeforeNavigate2(ASender: TObject;
  908.   const pDisp: IDispatch; const URL, Flags, TargetFrameName, PostData,
  909.   Headers: OleVariant; var Cancel: WordBool);
  910. begin
  911.   BeforeNavigate(URL, Cancel);
  912. end;
  913. (*
  914. {$ELSE}
  915. procedure TForm1.WebBrowser1BeforeNavigate2(ASender: TObject;
  916.   const pDisp: IDispatch; var URL, Flags, TargetFrameName, PostData,
  917.   Headers: OleVariant; var Cancel: WordBool);
  918. begin
  919.   BeforeNavigate(URL, Cancel);
  920. end;
  921. {$ENDIF}
  922. *)
  923.  
  924. procedure TForm1.WebBrowser1WindowClosing(ASender: TObject;
  925.   IsChildWindow: WordBool; var Cancel: WordBool);
  926. resourcestring
  927.   SCloseRequest = 'A script has requested the window to be closed. The window of a standalone script would now close.';
  928. begin
  929.   ShowMessage(SCloseRequest);
  930.   TWebBrowser(ASender).Clear;
  931.   Cancel := true;
  932. end;
  933.  
  934. procedure TForm1.BeforeNavigate(const URL: OleVariant; var Cancel: WordBool);
  935. var
  936.   s, myURL: string;
  937.   lineno: integer;
  938.   p: integer;
  939. begin
  940.   {$REGION 'Line number references (PHP errors and warnings)'}
  941.   if Copy(URL, 1, length(FASTPHP_GOTO_URI_PREFIX)) = FASTPHP_GOTO_URI_PREFIX then
  942.   begin
  943.     try
  944.       s := copy(URL, length(FASTPHP_GOTO_URI_PREFIX)+1, 99);
  945.       if not TryStrToInt(s, lineno) then exit;
  946.       GotoLineNo(lineno);
  947.       SynEditFocusTimer.Enabled := true;
  948.     finally
  949.       Cancel := true;
  950.     end;
  951.     Exit;
  952.   end;
  953.   {$ENDREGION}
  954.  
  955.   {$REGION 'Intelligent browser (executes PHP scripts which are clicked in a hyperlink)'}
  956.   if URL <> 'about:blank' then
  957.   begin
  958.     myUrl := URL;
  959.  
  960.     p := Pos('?', myUrl);
  961.     if p >= 1 then myURL := copy(myURL, 1, p-1);
  962.  
  963.     // TODO: myURL urldecode
  964.     // TODO: maybe we could even open that file in the editor!
  965.     // TODO: ?parameter=....
  966.  
  967.     if FileExists(myURL) and (EndsText('.php', myURL) or EndsText('.php3', myURL) or EndsText('.php4', myURL) or EndsText('.php5', myURL) or EndsText('.phps', myURL)) then
  968.     begin
  969.       WebBrowser1.LoadHTML(RunPHPScript(myURL), myUrl);
  970.       Cancel := true;
  971.     end;
  972.   end;
  973.   {$ENDREGION}
  974. end;
  975.  
  976. procedure TForm1.BtnLightClick(Sender: TObject);
  977. var
  978.   CanClose: boolean;
  979. begin
  980.   FormCloseQuery(Form1, CanClose);
  981.   if not CanClose then exit;
  982.  
  983.   if IsThemeDark then
  984.   begin
  985.     BtnLight.Picture.Assign(BtnLightOn.Picture);
  986.     Theme_Light;
  987.     TFastPHPConfig.DarkTheme := false;
  988.   end
  989.   else
  990.   begin
  991.     BtnLight.Picture.Assign(BtnLightOff.Picture);
  992.     Theme_Dark;
  993.     TFastPHPConfig.DarkTheme := true;
  994.   end;
  995. end;
  996.  
  997. procedure TForm1.BtnSpecialCharsClick(Sender: TObject);
  998. var
  999.   opts: TSynEditorOptions;
  1000. begin
  1001.   opts := SynEdit1.Options;
  1002.   if eoShowSpecialChars in SynEdit1.Options then
  1003.   begin
  1004.     BtnSpecialChars.Picture.Assign(BtnSpecialCharsOff.Picture);
  1005.     Exclude(opts, eoShowSpecialChars);
  1006.     TFastPHPConfig.SpecialChars := false;
  1007.   end
  1008.   else
  1009.   begin
  1010.     BtnSpecialChars.Picture.Assign(BtnSpecialCharsOn.Picture);
  1011.     Include(opts, eoShowSpecialChars);
  1012.     TFastPHPConfig.SpecialChars := true;
  1013.   end;
  1014.   SynEdit1.Options := opts;
  1015. end;
  1016.  
  1017. procedure TForm1.FileModTimerTimer(Sender: TObject);
  1018. resourcestring
  1019.   SChangeConflict = 'The file was changed in a different application BUT IT WAS ALSO MODIFIED HERE! Reload file AND LOSE CHANGES HERE?';
  1020.   SChangeNotify = 'The file was changed in a different application! Reload file?';
  1021. begin
  1022.   FileModTimer.Enabled := false;
  1023.   if FileModLast <> FileAge(GetScrapFile) then
  1024.   begin
  1025.     FileModLast := FileAge(GetScrapFile);
  1026.     if SynEdit1.Modified then
  1027.     begin
  1028.       if MessageDlg(SChangeConflict, mtWarning, mbYesNoCancel, 0) = mrYes then
  1029.       begin
  1030.         SynEdit1.Lines.LoadFromFile(GetScrapFile);
  1031.       end;
  1032.     end
  1033.     else
  1034.     begin
  1035.       if MessageDlg(SChangeNotify, mtConfirmation, mbYesNoCancel, 0) = mrYes then
  1036.       begin
  1037.         SynEdit1.Lines.LoadFromFile(GetScrapFile);
  1038.       end;
  1039.     end;
  1040.   end;
  1041.   FileModTimer.Enabled := true;
  1042. end;
  1043.  
  1044. procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
  1045. begin
  1046.   DragAcceptFiles(Handle, False);
  1047.   TFastPHPConfig.FontSize := SynEdit1.Font.Size;
  1048. end;
  1049.  
  1050. procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
  1051. var
  1052.   r: integer;
  1053. resourcestring
  1054.   SWantToSave = 'Do you want to save?';
  1055. begin
  1056.   if SynEdit1.Modified then
  1057.   begin
  1058.     if (ParamStr(1) <> '') or (FSaveAsFilename <> '') then
  1059.     begin
  1060.       r := MessageDlg(SWantToSave, mtConfirmation, mbYesNoCancel, 0);
  1061.       if r = mrCancel then
  1062.       begin
  1063.         CanClose := false;
  1064.         Exit;
  1065.       end
  1066.       else if r = mrYes then
  1067.       begin
  1068.         ActionSave.Execute;
  1069.         CanClose := true;
  1070.       end;
  1071.     end
  1072.     else
  1073.     begin
  1074.       ActionSave.Execute;
  1075.       CanClose := true;
  1076.     end;
  1077.   end;
  1078. end;
  1079.  
  1080. procedure TForm1.FormCreate(Sender: TObject);
  1081. var
  1082.   exeDir: string;
  1083.   sScrapFile: string;
  1084. begin
  1085.   HlpPrevPageIndex := -1;
  1086.   CurSearchTerm := '';
  1087.   sScrapFile := GetScrapFile;
  1088.   Caption := Caption + ' - ' + sScrapFile;
  1089.   Application.Title := Format('%s - FastPHP', [ExtractFileName(sScrapFile)]); // do not translate!
  1090.   SrcRep := TSynEditFindReplace.Create(self);
  1091.   SrcRep.Editor := SynEdit1;
  1092.   SynEdit1.Gutter.Gradient := HighColorWindows;
  1093.  
  1094.   Screen.Cursors[crMouseGutter] := LoadCursor(hInstance, 'MOUSEGUTTER');
  1095.   SynEdit1.Gutter.Cursor := crMouseGutter;
  1096.  
  1097.   exeDir := IncludeTrailingPathDelimiter(ExtractFilePath(ParamStr(0)));
  1098.   if FileExists(exeDir + 'codeexplorer.bmp') then ImageList1.LoadAndSplitImages(exeDir + 'codeexplorer.bmp');
  1099.  
  1100.   FileModLast := FileAge(sScrapFile);
  1101.   FileModTimer.Enabled := True;
  1102. end;
  1103.  
  1104. procedure TForm1.FormDestroy(Sender: TObject);
  1105. begin
  1106.   if Assigned(ChmIndex) then
  1107.   begin
  1108.     FreeAndNil(ChmIndex);
  1109.   end;
  1110.   FreeAndNil(SrcRep);
  1111.  
  1112.   if hMutex <> 0 then CloseHandle(hMutex); // Note: ReleaseMutex does not work as expected!
  1113.  
  1114.   if Assigned(codeExplorer) then
  1115.   begin
  1116.     codeExplorer.Terminate;
  1117.     codeExplorer.WaitFor;
  1118.     FreeAndNil(codeExplorer);
  1119.   end;
  1120. end;
  1121.  
  1122. procedure TForm1.FormShow(Sender: TObject);
  1123. var
  1124.   ScrapFile: string;
  1125.   tmpFontSize: integer;
  1126.   opts: TSynEditorOptions;
  1127. resourcestring
  1128.   SFileAlreadyOpen = 'File "%s" is alrady open!';
  1129. begin
  1130.   if FormShowRanOnce then exit; // If the theme is changed from normal to dark, OnShow will be called another time
  1131.   FormShowRanOnce := true;
  1132.  
  1133.   ScrapFile := GetScrapFile;
  1134.   if ScrapFile = '' then
  1135.   begin
  1136.     Application.Terminate; // Close;
  1137.     exit;
  1138.   end;
  1139.  
  1140.   opts := SynEdit1.Options;
  1141.   if TFastPHPConfig.SpecialChars then
  1142.   begin
  1143.     BtnSpecialChars.Picture.Assign(BtnSpecialCharsOn.Picture);
  1144.     Include(opts, eoShowSpecialChars);
  1145.   end
  1146.   else
  1147.   begin
  1148.     BtnSpecialChars.Picture.Assign(BtnSpecialCharsOff.Picture);
  1149.     Exclude(opts, eoShowSpecialChars);
  1150.   end;
  1151.   SynEdit1.Options := opts;
  1152.  
  1153.   if FileExists(ScrapFile) then
  1154.   begin
  1155.     if hMutex = 0 then
  1156.     begin
  1157.       hMutex := CreateMutex(nil, True, PChar('FastPHP'+md5(UpperCase(ScrapFile))));
  1158.       if GetLastError = ERROR_ALREADY_EXISTS then
  1159.       begin
  1160.         // TODO: It would be great if the window of that FastPHP instance would switched to foreground
  1161.         ShowMessageFmt(SFileAlreadyOpen, [ScrapFile]);
  1162.         Close;
  1163.       end;
  1164.  
  1165.       SynEdit1.Lines.LoadFromFile(ScrapFile);
  1166.     end;
  1167.   end
  1168.   else
  1169.     SynEdit1.Lines.Clear;
  1170.  
  1171.   PageControl1.ActivePage := PlaintextTabSheet;
  1172.  
  1173.   PageControl2.ActivePage := CodeTabsheet;
  1174.   HelpTabsheet.TabVisible := false;
  1175.  
  1176.   tmpFontSize := TFastPHPConfig.FontSize;
  1177.   if tmpFontSize <> -1 then SynEdit1.Font.Size := tmpFontSize;
  1178.   SynEdit1.SetFocus;
  1179.  
  1180.   DoubleBuffered := true;
  1181.   StartCodeExplorer;
  1182.  
  1183.   DragAcceptFiles(Handle, True);
  1184.  
  1185.   StartupTimer.Enabled := true;
  1186. end;
  1187.  
  1188. procedure TForm1.SaveToFile(filename: string);
  1189. var
  1190.   ss: TStringStream;
  1191.   ms: TMemoryStream;
  1192.   fs: TFileStream;
  1193.   eolStyle: string;
  1194.   str: string;
  1195. begin
  1196.   FileModTimer.Enabled := false;
  1197.  
  1198.   ms := TMemoryStream.Create;
  1199.   ss := TStringStream.Create('');
  1200.   fs := TFileStream.Create(filename, fmCreate);
  1201.   try
  1202.     // Save everything in a memory stream and then to a string
  1203.     // in comparison to "str := SynEdit1.Lines.Text;",
  1204.     // This approach should preserve LF / CRLF line endings
  1205.     SynEdit1.Lines.SaveToStream(ms);
  1206.     ms.Position := 0;
  1207.     ss.CopyFrom(ms, ms.Size);
  1208.     ss.Position := 0;
  1209.     str := ss.ReadString(ss.Size);
  1210.     ss.Size := 0; // clear string-stream, because we need it later again
  1211.  
  1212.     // Detect current line-endings
  1213.     if Copy(str, 1, 2) = '#!' then
  1214.     begin
  1215.       // Shebang. Use ONLY Linux LF
  1216.       str := StringReplace(str, #13#10, #10, [rfReplaceAll]);
  1217.       eolStyle := #10 // Linux LF
  1218.     end
  1219.     else
  1220.     begin
  1221.       if Pos(#13#10, str) > 0 then
  1222.         eolStyle := #13#10 // Windows CRLF
  1223.       else if Pos(#10, str) > 0 then
  1224.         eolStyle := #10 // Linux LF
  1225.       else
  1226.       begin
  1227.         if DefaultTextLineBreakStyle = tlbsLF then
  1228.           eolStyle := #10 // Linux LF
  1229.         else if DefaultTextLineBreakStyle = tlbsCRLF then
  1230.           eolStyle := #13#10 // Windows CRLF
  1231.         //else if DefaultTextLineBreakStyle = tlbsCR then
  1232.         //  eolStyle := #13 // Old Mac CR
  1233.         else
  1234.           eolStyle := #13#10; // (Should not happen)
  1235.       end;
  1236.     end;
  1237.  
  1238.     // Unitfy line-endings
  1239.     str := StringReplace(str, #13#10, eolStyle, [rfReplaceAll]);
  1240.     str := StringReplace(str, #10, eolStyle, [rfReplaceAll]);
  1241.     str := StringReplace(str, #13, '', [rfReplaceAll]);
  1242.  
  1243.     // Replace all trailing linebreaks by a single line break
  1244.     // Note: Removing all line breaks is not good, since Linux's "nano" will
  1245.     //       re-add a linebreak at the end of the file
  1246.     str := TrimRight(str) + eolStyle;
  1247.  
  1248.     // Old versions of Delphi/SynEdit write an UTF-8 BOM, which makes problems
  1249.     // e.g. with AJAX handlers (because AJAX reponses must not have a BOM).
  1250.     // So we try to avoid that.
  1251.     // Note that the output is still UTF-8 encoded if the input file was UTF-8 encoded
  1252.     if Copy(str,1,3) = #$EF#$BB#$BF then Delete(str, 1, 3);
  1253.  
  1254.     // Now save to the file
  1255.     ss.WriteString(str);
  1256.     ss.Position := 0;
  1257.     fs.CopyFrom(ss, ss.Size-ss.Position);
  1258.   finally
  1259.     FreeAndNil(ms);
  1260.     FreeAndNil(ss);
  1261.     FreeAndNil(fs);
  1262.   end;
  1263.  
  1264.   FileModLast := FileAge(GetScrapFile);
  1265.   FileModTimer.Enabled := True;
  1266. end;
  1267.  
  1268. procedure TForm1.StartCodeExplorer;
  1269. begin
  1270.   codeExplorer := TRunCodeExplorer.Create(true);
  1271.   codeExplorer.InputRequestCallback := InputRequestCallback;
  1272.   codeExplorer.OutputNotifyCallback := OutputNotifyCallback;
  1273.   codeExplorer.PhpExe := GetPHPExe;
  1274.   codeExplorer.PhpFile := IncludeTrailingPathDelimiter(ExtractFileDir(Application.ExeName)) + 'codeexplorer.php'; // GetScrapFile;
  1275.   codeExplorer.WorkDir := ExtractFileDir(Application.ExeName);
  1276.   codeExplorer.Resume;
  1277. end;
  1278.  
  1279. procedure TForm1.StartUpTimerTimer(Sender: TObject);
  1280. begin
  1281.   StartupTimer.Enabled := false;
  1282.  
  1283.   // We need this timer because we cannot change the Theme during OnShow,
  1284.   // because the Delphi VCL Theme is buggy!
  1285.  
  1286.   if TFastPHPConfig.DarkTheme then
  1287.   begin
  1288.     BtnLight.Picture.Assign(BtnLightOff.Picture);
  1289.     Theme_Dark;
  1290.   end
  1291.   else
  1292.   begin
  1293.     BtnLight.Picture.Assign(BtnLightOn.Picture);
  1294.     Theme_Light;
  1295.   end;
  1296. end;
  1297.  
  1298. function TForm1.GetScrapFile: string;
  1299. var
  1300.   tmpPath: string;
  1301. resourcestring
  1302.   SFileDoesNotExistsCreate = 'File %s does not exist. Create it?';
  1303.   SPathDoesNotExistTryAgain = 'Path does not exist! Please try again.';
  1304. begin
  1305.   if FSaveAsFilename <> '' then
  1306.   begin
  1307.     result := FSaveAsFilename;
  1308.     exit;
  1309.   end;
  1310.  
  1311.   if FScrapFile <> '' then
  1312.   begin
  1313.     result := FScrapFile;
  1314.     exit;
  1315.   end;
  1316.  
  1317.   if ParamStr(1) <> '' then
  1318.   begin
  1319.     // Program was started with a filename
  1320.  
  1321.     result := ParamStr(1);
  1322.  
  1323.     if not FileExists(result) then
  1324.     begin
  1325.       case MessageDlg(Format(SFileDoesNotExistsCreate, [result]), mtConfirmation, mbYesNoCancel, 0) of
  1326.         mrYes:
  1327.           try
  1328.             SaveToFile(result);
  1329.           except
  1330.             on E: Exception do
  1331.             begin
  1332.               MessageDlg(E.Message, mtError, [mbOk], 0);
  1333.               Application.Terminate;
  1334.               result := '';
  1335.               exit;
  1336.             end;
  1337.           end;
  1338.         mrNo:
  1339.           begin
  1340.             Application.Terminate;
  1341.             result := '';
  1342.             exit;
  1343.           end;
  1344.         mrCancel:
  1345.           begin
  1346.             Application.Terminate;
  1347.             result := '';
  1348.             exit;
  1349.           end;
  1350.       end;
  1351.     end;
  1352.   end
  1353.   else
  1354.   begin
  1355.     // Program is started without filename -> use scrap file
  1356.  
  1357.     result := TFastPHPConfig.ScrapFile;
  1358.  
  1359.     if not FileExists(result) then
  1360.     begin
  1361.       repeat
  1362.         {$REGION 'Determinate opendialog initial directory'}
  1363.         if result <> '' then
  1364.         begin
  1365.           tmpPath := ExtractFilePath(result);
  1366.           if DirectoryExists(tmpPath) then
  1367.           begin
  1368.             OpenDialog3.InitialDir := tmpPath;
  1369.             OpenDialog3.FileName := Result;
  1370.           end
  1371.           else
  1372.           begin
  1373.             OpenDialog3.InitialDir := GetMyDocumentsFolder;
  1374.           end;
  1375.         end
  1376.         else
  1377.         begin
  1378.           OpenDialog3.InitialDir := GetMyDocumentsFolder;
  1379.         end;
  1380.         {$ENDREGION}
  1381.  
  1382.         if not OpenDialog3.Execute then
  1383.         begin
  1384.           Application.Terminate;
  1385.           result := '';
  1386.           exit;
  1387.         end;
  1388.  
  1389.         if not DirectoryExists(ExtractFilePath(OpenDialog3.FileName)) then
  1390.         begin
  1391.           MessageDlg(SPathDoesNotExistTryAgain, mtWarning, [mbOk], 0);
  1392.         end
  1393.         else
  1394.         begin
  1395.           result := OpenDialog3.FileName;
  1396.         end;
  1397.       until result <> '';
  1398.  
  1399.       if not FileExists(result) then
  1400.       begin
  1401.         try
  1402.           // Try saving the file; check if we have permissions
  1403.           //SynEdit1.Lines.Clear;
  1404.           SaveToFile(result);
  1405.         except
  1406.           on E: Exception do
  1407.           begin
  1408.             MessageDlg(E.Message, mtError, [mbOk], 0);
  1409.             Application.Terminate;
  1410.             result := '';
  1411.             exit;
  1412.           end;
  1413.         end;
  1414.       end;
  1415.  
  1416.       TFastPHPConfig.ScrapFile := result;
  1417.       FScrapFile := result;
  1418.     end;
  1419.   end;
  1420. end;
  1421.  
  1422. procedure TForm1.Help;
  1423. var
  1424.   IndexFile, chmFile, w, OriginalWord, url: string;
  1425.   internalHtmlFile: string;
  1426. resourcestring
  1427.   SChmFileNotAValidPHPDocumentation = 'The CHM file is not a valid PHP documentation. Cannot use help.';
  1428.   SUnknownErrorCannotUseHelp = 'Unknown error. Cannot use help.';
  1429.   SNoHelpAvailable = 'No help for "%s" available';
  1430. begin
  1431.   if not Assigned(ChmIndex) then
  1432.   begin
  1433.     IndexFile := TFastPHPConfig.HelpIndex;
  1434.     IndexFile := ChangeFileExt(IndexFile, '.ini'); // Just to be sure. Maybe someone wrote manually the ".chm" file in there
  1435.     if FileExists(IndexFile) then
  1436.     begin
  1437.       ChmIndex := TMemIniFile.Create(IndexFile);
  1438.     end;
  1439.   end;
  1440.  
  1441.   if Assigned(ChmIndex) then
  1442.   begin
  1443.     IndexFile := TFastPHPConfig.HelpIndex;
  1444.     // We don't check if IndexFile still exists. It is not important since we have ChmIndex pre-loaded in memory
  1445.  
  1446.     chmFile := ChangeFileExt(IndexFile, '.chm');
  1447.     if not FileExists(chmFile) then
  1448.     begin
  1449.       FreeAndNil(ChmIndex);
  1450.     end;
  1451.   end;
  1452.  
  1453.   if not Assigned(ChmIndex) then
  1454.   begin
  1455.     if not OpenDialog1.Execute then exit;
  1456.  
  1457.     chmFile := OpenDialog1.FileName;
  1458.     if not FileExists(chmFile) then exit;
  1459.  
  1460.     IndexFile := ChangeFileExt(chmFile, '.ini');
  1461.  
  1462.     if not FileExists(IndexFile) then
  1463.     begin
  1464.       Panel1.Align := alClient;
  1465.       Panel1.Visible := true;
  1466.       Panel1.BringToFront;
  1467.       Screen.Cursor := crHourGlass;
  1468.       Application.ProcessMessages;
  1469.       try
  1470.         if not ParseCHM(chmFile) then
  1471.         begin
  1472.           MessageDlg(SChmFileNotAValidPHPDocumentation, mtError, [mbOk], 0);
  1473.           exit;
  1474.         end;
  1475.       finally
  1476.         Screen.Cursor := crDefault;
  1477.         Panel1.Visible := false;
  1478.       end;
  1479.  
  1480.       if not FileExists(IndexFile) then
  1481.       begin
  1482.         MessageDlg(SUnknownErrorCannotUseHelp, mtError, [mbOk], 0);
  1483.         exit;
  1484.       end;
  1485.     end;
  1486.  
  1487.     TFastPHPConfig.HelpIndex := IndexFile;
  1488.  
  1489.     ChmIndex := TMemIniFile.Create(IndexFile);
  1490.   end;
  1491.  
  1492.   w := GetWordUnderCaret(SynEdit1);
  1493.   if w = '' then exit;
  1494.   {$IFDEF UNICODE}
  1495.   if CharInSet(w[1], ['0'..'9']) then exit;
  1496.   {$ELSE}
  1497.   if w[1] in ['0'..'9'] then exit;
  1498.   {$ENDIF}
  1499.  
  1500.   Originalword := w;
  1501. //  w := StringReplace(w, '_', '-', [rfReplaceAll]);
  1502.   w := LowerCase(w);
  1503.   CurSearchTerm := w;
  1504.  
  1505.   internalHtmlFile := ChmIndex.ReadString('function', CurSearchTerm, ''); // do not translate
  1506.   if internalHtmlFile = '' then
  1507.     internalHtmlFile := ChmIndex.ReadString('_HelpWords_', CurSearchTerm, ''); // do not translate
  1508.   if internalHtmlFile = '' then
  1509.   begin
  1510.     HelpTabsheet.TabVisible := false;
  1511.     HlpPrevPageIndex := -1;
  1512.     ShowMessageFmt(SNoHelpAvailable, [Originalword]);
  1513.     Exit;
  1514.   end;
  1515.  
  1516.   url := 'mk:@MSITStore:'+ChmFile+'::'+internalHtmlFile;
  1517.  
  1518.   HlpPrevPageIndex := PageControl2.ActivePageIndex; // Return by pressing ESC
  1519.   HelpTabsheet.TabVisible := true;
  1520.   PageControl2.ActivePage := HelpTabsheet;
  1521.   WebBrowser2.Navigate(url);
  1522.   WebBrowser2.Wait;
  1523. end;
  1524.  
  1525. procedure TForm1.GotoLineNo(LineNo:integer);
  1526. var
  1527.   line: string;
  1528.   i: integer;
  1529. begin
  1530.   SynEdit1.GotoLineAndCenter(LineNo);
  1531.  
  1532.   // Skip indent
  1533.   line := SynEdit1.Lines[SynEdit1.CaretY];
  1534.   for i := 1 to Length(line) do
  1535.   begin
  1536.     {$IFDEF UNICODE}
  1537.     if not CharInSet(line[i], [' ', #9]) then
  1538.     {$ELSE}
  1539.     if not (line[i] in [' ', #9]) then
  1540.     {$ENDIF}
  1541.     begin
  1542.       SynEdit1.CaretX := i-1;
  1543.       break;
  1544.     end;
  1545.   end;
  1546.  
  1547.   PageControl2.ActivePage := CodeTabsheet;
  1548.   if SynEdit1.CanFocus then SynEdit1.SetFocus;
  1549. end;
  1550.  
  1551. procedure TForm1.PageControl2Changing(Sender: TObject;
  1552.   var AllowChange: Boolean);
  1553. begin
  1554.   if PageControl2.ActivePage = HelpTabsheet then
  1555.     HlpPrevPageIndex := -1
  1556.   else
  1557.     HlpPrevPageIndex := PageControl2.ActivePageIndex;
  1558.  
  1559.   AllowChange := true;
  1560. end;
  1561.  
  1562. procedure TForm1.Memo2DblClick(Sender: TObject);
  1563. var
  1564.   line: string;
  1565.  
  1566.   procedure _process(toFind: string);
  1567.   var
  1568.     p, lineno: integer;
  1569.   begin
  1570.     if FileSystemCaseSensitive then
  1571.       p := Pos(toFind, line)
  1572.     else
  1573.       p := Pos(LowerCase(toFind), LowerCase(line));
  1574.     if p <> 0 then
  1575.     begin
  1576.       line := copy(line, p+length(toFind), 99);
  1577.       if not TryStrToInt(line, lineno) then exit;
  1578.       GotoLineNo(lineno);
  1579.     end;
  1580.   end;
  1581.  
  1582. begin
  1583.   line := memo2.Lines.Strings[Memo2.CaretPos.Y];
  1584.  
  1585.   {$REGION 'Possibility 1: filename.php:lineno'}
  1586.   _process(ExtractFileName(GetScrapFile) + ':');
  1587.   {$ENDREGION}
  1588.  
  1589.   {$REGION 'Possibility 2: on line xx'}
  1590.   _process(ExtractFileName(GetScrapFile) + ' on line '); // do not translate!
  1591.   {$ENDREGION}
  1592. end;
  1593.  
  1594. procedure TForm1.Memo2KeyDown(Sender: TObject; var Key: Word;
  1595.   Shift: TShiftState);
  1596. begin
  1597.   if ((ssCtrl in Shift) and (Key = 65)) then TMemo(Sender).SelectAll;
  1598. end;
  1599.  
  1600. function TForm1.MarkUpLineReference(cont: string): string;
  1601.  
  1602.   procedure _process(toFind: string);
  1603.   var
  1604.     p, a, b: integer;
  1605.     num: integer;
  1606.     insert_a, insert_b: string;
  1607.   begin
  1608.     if FileSystemCaseSensitive then
  1609.       p := Pos(toFind, cont)
  1610.     else
  1611.       p := Pos(LowerCase(toFind), LowerCase(cont));
  1612.     while p >= 1 do
  1613.     begin
  1614.       a := p;
  1615.       b := p + length(toFind);
  1616.       num := 0;
  1617.       {$IFDEF UNICODE}
  1618.       while CharInSet(cont[b], ['0'..'9']) do
  1619.       {$ELSE}
  1620.       while cont[b] in ['0'..'9'] do
  1621.       {$ENDIF}
  1622.       begin
  1623.         num := num*10 + StrToInt(cont[b]);
  1624.         inc(b);
  1625.       end;
  1626.  
  1627.       insert_b := '</a>';
  1628.       insert_a := '<a href="' + FASTPHP_GOTO_URI_PREFIX + IntToStr(num) + '">';
  1629.  
  1630.       insert(insert_b, cont, b);
  1631.       insert(insert_a, cont, a);
  1632.  
  1633.       p := b + Length(insert_a) + Length(insert_b);
  1634.  
  1635.       p := PosEx(toFind, cont, p+1);
  1636.     end;
  1637.   end;
  1638.  
  1639. begin
  1640.   {$REGION 'Possibility 1: filename.php:lineno'}
  1641.   _process(ExtractFileName(GetScrapFile) + ':');
  1642.   {$ENDREGION}
  1643.  
  1644.   {$REGION 'Possibility 2: on line xx'}
  1645.   _process(ExtractFileName(GetScrapFile) + ' on line '); // do not translate!
  1646.   {$ENDREGION}
  1647.  
  1648.   result := cont;
  1649. end;
  1650.  
  1651. function TForm1.InputRequestCallback(var data: AnsiString): boolean;
  1652. begin
  1653.   data := UTF8Encode(SynEdit1.Text);
  1654.   result := true;
  1655. end;
  1656.  
  1657. function TForm1.IsThemeDark: boolean;
  1658. begin
  1659.   result := Assigned(TStyleManager.ActiveStyle) and (TStyleManager.ActiveStyle.Name<>'Windows');
  1660. end;
  1661.  
  1662. function TForm1.OutputNotifyCallback(const data: AnsiString): boolean;
  1663. begin
  1664.   result := TreeView1.FillWithFastPHPData(string(data));
  1665. end;
  1666.  
  1667. end.
  1668.