Subversion Repositories fastphp

Rev

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