Subversion Repositories fastphp

Rev

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

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