Subversion Repositories fastphp

Rev

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