Subversion Repositories fastphp

Rev

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