Subversion Repositories fastphp

Rev

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