Subversion Repositories fastphp

Rev

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