Subversion Repositories fastphp

Rev

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