Subversion Repositories fastphp

Rev

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

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