Subversion Repositories fastphp

Rev

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

  1. unit EditorMain;
  2.  
  3. {$Include 'FastPHP.inc'}
  4.  
  5. (*
  6.   This program requires
  7.   - Microsoft Internet Controls (TWebBrowser)
  8.     If you are using Delphi 10.1 Starter Edition, please import the ActiveX TLB
  9.     "Microsoft Internet Controls"
  10.   - SynEdit
  11.     You can obtain SynEdit via Embarcadero GetIt
  12. *)
  13.  
  14. // TODO: localize
  15. // TODO: wieso geht copy paste im twebbrowser nicht???
  16. // Wieso dauert webbrowser1 erste kompilierung so lange???
  17. // TODO: wieso kommt syntax fehler zweimal? einmal stderr einmal stdout?
  18. // TODO: Browser titlebar (link preview)
  19. // TODO: todo liste
  20.  
  21. // Future ideas
  22. // - code explorer / code insight
  23. // - verschiedene php versionen?
  24. // - webbrowser1 nur laden, wenn man den tab anwählt?
  25. // - doppelklick auf tab soll diesen schließen
  26. // - Onlinehelp (www) aufrufen
  27. // - Let all colors be adjustable
  28. // - code in bildschirmmitte (horizontal)?
  29.  
  30. interface
  31.  
  32. uses
  33.   // TODO: "{$IFDEF USE_SHDOCVW_TLB}_TLB{$ENDIF}" does not work with Delphi 10.2
  34.   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  35.   Dialogs, StdCtrls, OleCtrls, ComCtrls, ExtCtrls, ToolWin, IniFiles,
  36.   SynEditHighlighter, SynHighlighterPHP, SynEdit, ShDocVw_TLB, FindReplace,
  37.   System.Actions, Vcl.ActnList, System.UITypes, SynEditMiscClasses,
  38.   SynEditSearch, RunPHP;
  39.  
  40. {.$DEFINE OnlineHelp}
  41.  
  42. type
  43.   TForm1 = class(TForm)
  44.     PageControl1: TPageControl;
  45.     PlaintextTabSheet: TTabSheet;
  46.     HtmlTabSheet: TTabSheet;
  47.     Memo2: TMemo;
  48.     WebBrowser1: TWebBrowser;
  49.     Splitter1: TSplitter;
  50.     PageControl2: TPageControl;
  51.     CodeTabsheet: TTabSheet;
  52.     HelpTabsheet: TTabSheet;
  53.     WebBrowser2: TWebBrowser;
  54.     OpenDialog1: TOpenDialog;
  55.     Panel1: TPanel;
  56.     OpenDialog3: TOpenDialog;
  57.     SynEdit1: TSynEdit;
  58.     SynPHPSyn1: TSynPHPSyn;
  59.     Panel2: TPanel;
  60.     SynEditFocusTimer: TTimer;
  61.     Button1: TButton;
  62.     Button2: TButton;
  63.     Button3: TButton;
  64.     Button4: TButton;
  65.     Button5: TButton;
  66.     Button6: TButton;
  67.     ActionList: TActionList;
  68.     ActionFind: TAction;
  69.     ActionReplace: TAction;
  70.     ActionFindNext: TAction;
  71.     ActionGoto: TAction;
  72.     ActionSave: TAction;
  73.     ActionHelp: TAction;
  74.     ActionRun: TAction;
  75.     ActionESC: TAction;
  76.     Button7: TButton;
  77.     ActionOpen: TAction;
  78.     Button8: TButton;
  79.     Button9: TButton;
  80.     ActionFindPrev: TAction;
  81.     Timer1: TTimer;
  82.     ActionSpaceToTab: TAction;
  83.     Button11: TButton;
  84.     SynEditSearch1: TSynEditSearch;
  85.     TreeView1: TTreeView;
  86.     Splitter2: TSplitter;
  87.     procedure Run(Sender: TObject);
  88.     procedure FormShow(Sender: TObject);
  89.     procedure FormCreate(Sender: TObject);
  90.     procedure FormDestroy(Sender: TObject);
  91.     procedure FormClose(Sender: TObject; var Action: TCloseAction);
  92.     procedure PageControl2Changing(Sender: TObject; var AllowChange: Boolean);
  93.     procedure Memo2DblClick(Sender: TObject);
  94.     procedure WebBrowser1BeforeNavigate2(ASender: TObject;
  95.       const pDisp: IDispatch; const URL, Flags, TargetFrameName, PostData,
  96.       Headers: OleVariant; var Cancel: WordBool);
  97.     procedure SynEditFocusTimerTimer(Sender: TObject);
  98.     procedure ActionFindExecute(Sender: TObject);
  99.     procedure ActionReplaceExecute(Sender: TObject);
  100.     procedure ActionFindNextExecute(Sender: TObject);
  101.     procedure ActionGotoExecute(Sender: TObject);
  102.     procedure ActionSaveExecute(Sender: TObject);
  103.     procedure ActionHelpExecute(Sender: TObject);
  104.     procedure ActionRunExecute(Sender: TObject);
  105.     procedure ActionESCExecute(Sender: TObject);
  106.     procedure SynEdit1MouseWheelDown(Sender: TObject; Shift: TShiftState;
  107.       MousePos: TPoint; var Handled: Boolean);
  108.     procedure SynEdit1MouseWheelUp(Sender: TObject; Shift: TShiftState;
  109.       MousePos: TPoint; var Handled: Boolean);
  110.     procedure ActionOpenExecute(Sender: TObject);
  111.     procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
  112.     procedure Memo2KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  113.     procedure ActionFindPrevExecute(Sender: TObject);
  114.     procedure SynEdit1MouseCursor(Sender: TObject;
  115.       const aLineCharPos: TBufferCoord; var aCursor: TCursor);
  116.     procedure Timer1Timer(Sender: TObject);
  117.     procedure ActionSpaceToTabExecute(Sender: TObject);
  118.     procedure TreeView1DblClick(Sender: TObject);
  119.   private
  120.     CurSearchTerm: string;
  121.     HlpPrevPageIndex: integer;
  122.     SrcRep: TSynEditFindReplace;
  123.     {$IFDEF OnlineHelp}
  124.     gOnlineHelpWord: string;
  125.     {$ENDIF}
  126.     procedure Help;
  127.     function MarkUpLineReference(cont: string): string;
  128.     function InputRequestCallback: string;
  129.     procedure OutputNotifyCallback(const data: string);
  130.   protected
  131.     ChmIndex: TMemIniFile;
  132.     FScrapFile: string;
  133.     codeExplorer: TRunCodeExplorer;
  134.     procedure GotoLineNo(LineNo: integer);
  135.     function GetScrapFile: string;
  136.     procedure StartCodeExplorer;
  137.   end;
  138.  
  139. var
  140.   Form1: TForm1;
  141.  
  142. implementation
  143.  
  144. {$R *.dfm}
  145.  
  146. uses
  147.   Functions, StrUtils, WebBrowserUtils, FastPHPUtils, Math, ShellAPI, RichEdit,
  148.   FastPHPTreeView;
  149.  
  150. // TODO: FindPrev ?
  151. procedure TForm1.ActionFindNextExecute(Sender: TObject);
  152. begin
  153.   SrcRep.FindNext;
  154. end;
  155.  
  156. procedure TForm1.ActionFindPrevExecute(Sender: TObject);
  157. begin
  158.   SrcRep.FindPrev;
  159. end;
  160.  
  161. procedure TForm1.ActionGotoExecute(Sender: TObject);
  162. var
  163.   val: string;
  164.   lineno: integer;
  165. begin
  166.   // TODO: VK_LMENU does not work! only works with AltGr but not Alt
  167.   // http://stackoverflow.com/questions/16828250/delphi-xe2-how-to-prevent-the-alt-key-stealing-focus ?
  168.  
  169.   InputQuery('Go to', 'Line number:', val);
  170.   if not TryStrToInt(val, lineno) then
  171.   begin
  172.     if SynEdit1.CanFocus then SynEdit1.SetFocus;
  173.     exit;
  174.   end;
  175.   GotoLineNo(lineno);
  176. end;
  177.  
  178. procedure TForm1.ActionHelpExecute(Sender: TObject);
  179. begin
  180.   Help;
  181.   if PageControl2.ActivePage = HelpTabsheet then
  182.     WebBrowser2.SetFocus
  183.   else if PageControl2.ActivePage = CodeTabsheet then
  184.     SynEdit1.SetFocus;
  185. end;
  186.  
  187. procedure TForm1.ActionOpenExecute(Sender: TObject);
  188. begin
  189.   If OpenDialog3.Execute then
  190.   begin
  191.     ShellExecute(0, 'open', PChar(ParamStr(0)), PChar(OpenDialog3.FileName), '', SW_NORMAL);
  192.   end;
  193. end;
  194.  
  195. procedure TForm1.ActionReplaceExecute(Sender: TObject);
  196. begin
  197.   SrcRep.ReplaceExecute;
  198. end;
  199.  
  200. procedure TForm1.ActionRunExecute(Sender: TObject);
  201. begin
  202.   Run(Sender);
  203.   SynEdit1.SetFocus;
  204. end;
  205.  
  206. procedure TForm1.ActionSaveExecute(Sender: TObject);
  207. begin
  208.   SynEdit1.Lines.SaveToFile(GetScrapFile);
  209.   SynEdit1.Modified := false;
  210. end;
  211.  
  212. procedure TForm1.ActionSpaceToTabExecute(Sender: TObject);
  213.  
  214.     function SpacesAtBeginning(line: string): integer;
  215.     begin
  216.       if line.Trim = '' then exit(0);
  217.       result := 0;
  218.       while line[result+1] = ' ' do
  219.       begin
  220.         inc(result);
  221.       end;
  222.     end;
  223.  
  224.     function GuessIndent(lines: TStrings): integer;
  225.       function _Check(indent: integer): boolean;
  226.       var
  227.         i: integer;
  228.       begin
  229.         result := true;
  230.         for i := 0 to lines.Count-1 do
  231.           if SpacesAtBeginning(lines.Strings[i]) mod indent <> 0 then
  232.           begin
  233.             // ShowMessageFmt('Zeile "%s" nicht durch %d teilbar!', [lines.strings[i], indent]);
  234.             exit(false);
  235.           end;
  236.       end;
  237.     var
  238.       i: integer;
  239.     begin
  240.       for i := 8 downto 2 do
  241.       begin
  242.         if _Check(i) then exit(i);
  243.       end;
  244.       result := -1;
  245.     end;
  246.  
  247.     procedure SpaceToTab(lines: TStrings; indent: integer);
  248.     var
  249.       i, spaces: integer;
  250.     begin
  251.       for i := 0 to lines.Count-1 do
  252.       begin
  253.         spaces := SpacesAtBeginning(lines.Strings[i]);
  254.         lines.Strings[i] := StringOfChar(#9, spaces div indent) + StringOfChar(' ', spaces mod indent) + Copy(lines.Strings[i], spaces+1, Length(lines.Strings[i])-spaces);
  255.       end;
  256.     end;
  257.  
  258.     function SpacesAvailable(lines: TStrings): boolean;
  259.     var
  260.       i, spaces: integer;
  261.     begin
  262.       for i := 0 to lines.Count-1 do
  263.       begin
  264.         spaces := SpacesAtBeginning(lines.Strings[i]);
  265.         if spaces > 0 then exit(true);
  266.       end;
  267.       exit(false);
  268.     end;
  269.  
  270. var
  271.   val: string;
  272.   ind: integer;
  273. resourcestring
  274.   SNoLinesAvailable = 'No lines with spaces at the beginning available';
  275. begin
  276.   // TODO: if something is selected, only process the selected part
  277.  
  278.   if not SpacesAvailable(SynEdit1.Lines) then
  279.   begin
  280.     ShowMessage(SNoLinesAvailable);
  281.     exit;
  282.   end;
  283.  
  284.   ind := GuessIndent(SynEdit1.Lines);
  285.   if ind <> -1 then val := IntToStr(ind);
  286.  
  287.   InputQuery('Spaces to tabs', 'Indent:', val); // TODO: handle CANCEL correctly...
  288.   if TryStrToInt(val.Trim, ind) then
  289.   begin
  290.     if ind = 0 then exit;
  291.     SpaceToTab(SynEdit1.Lines, ind);
  292.   end;
  293.  
  294.   if SynEdit1.CanFocus then SynEdit1.SetFocus;
  295. end;
  296.  
  297. procedure TForm1.ActionESCExecute(Sender: TObject);
  298. begin
  299.   if (HlpPrevPageIndex <> -1) and (PageControl2.ActivePage = HelpTabSheet) and
  300.      (HelpTabsheet.TabVisible) then
  301.   begin
  302.     PageControl2.ActivePageIndex := HlpPrevPageIndex;
  303.     HelpTabsheet.TabVisible := false;
  304.   end;
  305.  
  306.   // Dirty hack...
  307.   SrcRep.CloseDialogs;
  308. end;
  309.  
  310. procedure TForm1.ActionFindExecute(Sender: TObject);
  311. begin
  312.   SrcRep.FindExecute;
  313. end;
  314.  
  315. var
  316.   firstTimeBrowserLoad: boolean = true;
  317. procedure TForm1.Run(Sender: TObject);
  318. var
  319.   bakTS: TTabSheet;
  320. begin
  321.   memo2.Lines.Text := '';
  322.  
  323.   if firstTimeBrowserLoad then
  324.   begin
  325.     bakTS := PageControl1.ActivePage;
  326.     try
  327.       PageControl1.ActivePage := HtmlTabSheet; // Required for the first time, otherwise, WebBrowser1.Clear will hang
  328.       Webbrowser1.Clear;
  329.     finally
  330.       PageControl1.ActivePage := bakTS;
  331.     end;
  332.     firstTimeBrowserLoad := false;
  333.   end
  334.   else
  335.     Webbrowser1.Clear;
  336.  
  337.   Screen.Cursor := crHourGlass;
  338.   Application.ProcessMessages;
  339.  
  340.   try
  341.     SynEdit1.Lines.SaveToFile(GetScrapFile);
  342.  
  343.     memo2.Lines.Text := RunPHPScript(GetScrapFile);
  344.  
  345.     Webbrowser1.LoadHTML(MarkUpLineReference(memo2.Lines.Text), GetScrapFile);
  346.  
  347.     if IsTextHTML(memo2.lines.text) then
  348.       PageControl1.ActivePage := HtmlTabSheet
  349.     else
  350.       PageControl1.ActivePage := PlaintextTabSheet;
  351.   finally
  352.     Screen.Cursor := crDefault;
  353.   end;
  354. end;
  355.  
  356. procedure TForm1.SynEdit1MouseCursor(Sender: TObject; const aLineCharPos: TBufferCoord; var aCursor: TCursor);
  357. {$IFDEF OnlineHelp}
  358. var
  359.   Line: Integer;
  360.   Column: Integer;
  361.   word: string;
  362. begin
  363.   Line  := aLineCharPos.Line-1;
  364.   Column := aLineCharPos.Char-1;
  365.   word := GetWordUnderPos(TSynEdit(Sender), Line, Column);
  366.   if word <> gOnlineHelpWord then
  367.   begin
  368.     gOnlineHelpWord := word;
  369.     Timer1.Enabled := false;
  370.     Timer1.Enabled := true;
  371.   end;
  372. {$ELSE}
  373. begin
  374. {$ENDIF}
  375. end;
  376.  
  377. procedure TForm1.SynEdit1MouseWheelDown(Sender: TObject; Shift: TShiftState;
  378.   MousePos: TPoint; var Handled: Boolean);
  379. begin
  380.   if ssCtrl in Shift then
  381.   begin
  382.     SynEdit1.Font.Size := Max(SynEdit1.Font.Size - 1, 5);
  383.     Handled := true;
  384.   end
  385.   else Handled := false;
  386. end;
  387.  
  388. procedure TForm1.SynEdit1MouseWheelUp(Sender: TObject; Shift: TShiftState;
  389.   MousePos: TPoint; var Handled: Boolean);
  390. begin
  391.   if ssCtrl in Shift then
  392.   begin
  393.     SynEdit1.Font.Size := SynEdit1.Font.Size + 1;
  394.     Handled := true;
  395.   end
  396.   else Handled := false;
  397. end;
  398.  
  399. procedure TForm1.SynEditFocusTimerTimer(Sender: TObject);
  400. begin
  401.   SynEditFocusTimer.Enabled := false;
  402.   Button1.SetFocus; // Workaround for weird bug... This (and the timer) is necessary to get the focus to SynEdit1
  403.   SynEdit1.SetFocus;
  404. end;
  405.  
  406. procedure TForm1.Timer1Timer(Sender: TObject);
  407. begin
  408.   {$IFDEF OnlineHelp}
  409.   Timer1.Enabled := false;
  410.  
  411.   // TODO: Insert a small online help hint
  412.   //Caption := gOnlineHelpWord;
  413.   {$ENDIF}
  414. end;
  415.  
  416. procedure TForm1.TreeView1DblClick(Sender: TObject);
  417. var
  418.   tn: TTreeNode;
  419. begin
  420.   tn := TTreeView(Sender).Selected;
  421.   if tn = nil then exit;
  422.   GotoLineNo(Integer(tn.Data));
  423. end;
  424.  
  425. procedure TForm1.WebBrowser1BeforeNavigate2(ASender: TObject;
  426.   const pDisp: IDispatch; const URL, Flags, TargetFrameName, PostData,
  427.   Headers: OleVariant; var Cancel: WordBool);
  428. var
  429.   s, myURL: string;
  430.   lineno: integer;
  431.   p: integer;
  432. begin
  433.   {$REGION 'Line number references (PHP errors and warnings)'}
  434.   if Copy(URL, 1, length(FASTPHP_GOTO_URI_PREFIX)) = FASTPHP_GOTO_URI_PREFIX then
  435.   begin
  436.     try
  437.       s := copy(URL, length(FASTPHP_GOTO_URI_PREFIX)+1, 99);
  438.       if not TryStrToInt(s, lineno) then exit;
  439.       GotoLineNo(lineno);
  440.       SynEditFocusTimer.Enabled := true;
  441.     finally
  442.       Cancel := true;
  443.     end;
  444.     Exit;
  445.   end;
  446.   {$ENDREGION}
  447.  
  448.   {$REGION 'Intelligent browser (executes PHP scripts)'}
  449.   if URL <> 'about:blank' then
  450.   begin
  451.     myUrl := URL;
  452.  
  453.     p := Pos('?', myUrl);
  454.     if p >= 1 then myURL := copy(myURL, 1, p-1);
  455.  
  456.     // TODO: myURL urldecode
  457.     // TODO: maybe we could even open that file in the editor!
  458.  
  459.     if FileExists(myURL) and (EndsText('.php', myURL) or EndsText('.php3', myURL) or EndsText('.php4', myURL) or EndsText('.php5', myURL) or EndsText('.phps', myURL)) then
  460.     begin
  461.       WebBrowser1.LoadHTML(RunPHPScript(myURL), myUrl);
  462.       Cancel := true;
  463.     end;
  464.   end;
  465.   {$ENDREGION}
  466. end;
  467.  
  468. procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
  469. begin
  470.   FastPHPConfig.WriteInteger('User', 'FontSize', SynEdit1.Font.Size);
  471. end;
  472.  
  473. procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
  474. var
  475.   r: integer;
  476. begin
  477.   if SynEdit1.Modified then
  478.   begin
  479.     if ParamStr(1) <> '' then
  480.     begin
  481.       r := MessageDlg('Do you want to save?', mtConfirmation, mbYesNoCancel, 0);
  482.       if r = mrCancel then
  483.       begin
  484.         CanClose := false;
  485.         Exit;
  486.       end
  487.       else if r = mrYes then
  488.       begin
  489.         SynEdit1.Lines.SaveToFile(GetScrapFile);
  490.         CanClose := true;
  491.       end;
  492.     end
  493.     else
  494.     begin
  495.       SynEdit1.Lines.SaveToFile(GetScrapFile);
  496.       CanClose := true;
  497.     end;
  498.   end;
  499. end;
  500.  
  501. procedure TForm1.FormCreate(Sender: TObject);
  502. begin
  503.   HlpPrevPageIndex := -1;
  504.   CurSearchTerm := '';
  505.   Caption := Caption + ' - ' + GetScrapFile;
  506.   SrcRep := TSynEditFindReplace.Create(self);
  507.   SrcRep.Editor := SynEdit1;
  508. end;
  509.  
  510. procedure TForm1.FormDestroy(Sender: TObject);
  511. begin
  512.   if Assigned(ChmIndex) then
  513.   begin
  514.     FreeAndNil(ChmIndex);
  515.   end;
  516.   FreeAndNil(SrcRep);
  517.  
  518.   if Assigned(codeExplorer) then
  519.   begin
  520.     codeExplorer.Terminate;
  521.     codeExplorer.WaitFor;
  522.     FreeAndNil(codeExplorer);
  523.   end;
  524. end;
  525.  
  526. procedure TForm1.FormShow(Sender: TObject);
  527. var
  528.   ScrapFile: string;
  529. begin
  530.   ScrapFile := GetScrapFile;
  531.   if ScrapFile = '' then
  532.   begin
  533.     Application.Terminate; // Close;
  534.     exit;
  535.   end;
  536.   if FileExists(ScrapFile) then
  537.     SynEdit1.Lines.LoadFromFile(ScrapFile)
  538.   else
  539.     SynEdit1.Lines.Clear;
  540.  
  541.   PageControl1.ActivePage := PlaintextTabSheet;
  542.  
  543.   PageControl2.ActivePage := CodeTabsheet;
  544.   HelpTabsheet.TabVisible := false;
  545.  
  546.   SynEdit1.Font.Size := FastPHPConfig.ReadInteger('User', 'FontSize', SynEdit1.Font.Size);
  547.   SynEdit1.SetFocus;
  548.  
  549.   DoubleBuffered := true;
  550.   StartCodeExplorer;
  551. end;
  552.  
  553. procedure TForm1.StartCodeExplorer;
  554. begin
  555.   codeExplorer := TRunCodeExplorer.Create(true);
  556.   codeExplorer.InputRequestCallback := InputRequestCallback;
  557.   codeExplorer.OutputNotifyCallback := OutputNotifyCallback;
  558.   codeExplorer.PhpExe := GetPHPExe;
  559.   codeExplorer.PhpFile := IncludeTrailingPathDelimiter(ExtractFileDir(Application.ExeName)) + 'codeexplorer.php'; // GetScrapFile;
  560.   codeExplorer.WorkDir := ExtractFileDir(Application.ExeName);
  561.   codeExplorer.Start;
  562. end;
  563.  
  564. function TForm1.GetScrapFile: string;
  565. begin
  566.   if FScrapFile <> '' then exit(FScrapFile);
  567.  
  568.   if ParamStr(1) <> '' then
  569.     result := ParamStr(1)
  570.   else
  571.     result := FastPHPConfig.ReadString('Paths', 'ScrapFile', '');
  572.   if not FileExists(result) then
  573.   begin
  574.     repeat
  575.       if not OpenDialog3.Execute then
  576.       begin
  577.         Application.Terminate;
  578.         exit('');
  579.       end;
  580.  
  581.       if not DirectoryExists(ExtractFilePath(OpenDialog3.FileName)) then
  582.       begin
  583.         ShowMessage('Path does not exist! Please try again.');
  584.       end
  585.       else
  586.       begin
  587.         result := OpenDialog3.FileName;
  588.       end;
  589.     until result <> '';
  590.  
  591.     SynEdit1.Lines.Clear;
  592.     SynEdit1.Lines.SaveToFile(result);
  593.  
  594.     FastPHPConfig.WriteString('Paths', 'ScrapFile', result);
  595.     FScrapFile := result;
  596.   end;
  597. end;
  598.  
  599. procedure TForm1.Help;
  600. var
  601.   IndexFile, chmFile, w, OriginalWord, url: string;
  602.   internalHtmlFile: string;
  603. begin
  604.   if not Assigned(ChmIndex) then
  605.   begin
  606.     IndexFile := FastPHPConfig.ReadString('Paths', 'HelpIndex', '');
  607.     IndexFile := ChangeFileExt(IndexFile, '.ini'); // Just to be sure. Maybe someone wrote manually the ".chm" file in there
  608.     if FileExists(IndexFile) then
  609.     begin
  610.       ChmIndex := TMemIniFile.Create(IndexFile);
  611.     end;
  612.   end;
  613.  
  614.   if Assigned(ChmIndex) then
  615.   begin
  616.     IndexFile := FastPHPConfig.ReadString('Paths', 'HelpIndex', '');
  617.     // We don't check if IndexFile still exists. It is not important since we have ChmIndex pre-loaded in memory
  618.  
  619.     chmFile := ChangeFileExt(IndexFile, '.chm');
  620.     if not FileExists(chmFile) then
  621.     begin
  622.       FreeAndNil(ChmIndex);
  623.     end;
  624.   end;
  625.  
  626.   if not Assigned(ChmIndex) then
  627.   begin
  628.     if not OpenDialog1.Execute then exit;
  629.  
  630.     chmFile := OpenDialog1.FileName;
  631.     if not FileExists(chmFile) then exit;
  632.  
  633.     IndexFile := ChangeFileExt(chmFile, '.ini');
  634.  
  635.     if not FileExists(IndexFile) then
  636.     begin
  637.       Panel1.Align := alClient;
  638.       Panel1.Visible := true;
  639.       Panel1.BringToFront;
  640.       Screen.Cursor := crHourGlass;
  641.       Application.ProcessMessages;
  642.       try
  643.         if not ParseCHM(chmFile) then
  644.         begin
  645.           ShowMessage('The CHM file is not a valid PHP documentation. Cannot use help.');
  646.           exit;
  647.         end;
  648.       finally
  649.         Screen.Cursor := crDefault;
  650.         Panel1.Visible := false;
  651.       end;
  652.  
  653.       if not FileExists(IndexFile) then
  654.       begin
  655.         ShowMessage('Unknown error. Cannot use help.');
  656.         exit;
  657.       end;
  658.     end;
  659.  
  660.     FastPHPConfig.WriteString('Paths', 'HelpIndex', IndexFile);
  661.     FastPHPConfig.UpdateFile;
  662.  
  663.     ChmIndex := TMemIniFile.Create(IndexFile);
  664.   end;
  665.  
  666.   w := GetWordUnderCaret(SynEdit1);
  667.   if w = '' then exit;
  668.   if CharInSet(w[1], ['0'..'9']) then exit;
  669.  
  670.   Originalword := w;
  671. //  w := StringReplace(w, '_', '-', [rfReplaceAll]);
  672.   w := LowerCase(w);
  673.   CurSearchTerm := w;
  674.  
  675.   internalHtmlFile := ChmIndex.ReadString('_HelpWords_', CurSearchTerm, '');
  676.   if internalHtmlFile = '' then
  677.   begin
  678.     HelpTabsheet.TabVisible := false;
  679.     HlpPrevPageIndex := -1;
  680.     ShowMessageFmt('No help for "%s" available', [Originalword]);
  681.     Exit;
  682.   end;
  683.  
  684.   url := 'mk:@MSITStore:'+ChmFile+'::'+internalHtmlFile;
  685.  
  686.   HlpPrevPageIndex := PageControl2.ActivePageIndex; // Return by pressing ESC
  687.   HelpTabsheet.TabVisible := true;
  688.   PageControl2.ActivePage := HelpTabsheet;
  689.   WebBrowser2.Navigate(url);
  690.   WebBrowser2.Wait;
  691. end;
  692.  
  693. procedure TForm1.GotoLineNo(LineNo:integer);
  694. var
  695.   line: string;
  696.   i: integer;
  697. begin
  698.   SynEdit1.GotoLineAndCenter(LineNo);
  699.  
  700.   // Skip indent
  701.   line := SynEdit1.Lines[SynEdit1.CaretY];
  702.   for i := 1 to Length(line) do
  703.   begin
  704.     if not CharInSet(line[i], [' ', #9]) then
  705.     begin
  706.       SynEdit1.CaretX := i-1;
  707.       break;
  708.     end;
  709.   end;
  710.  
  711.   PageControl2.ActivePage := CodeTabsheet;
  712.   if SynEdit1.CanFocus then SynEdit1.SetFocus;
  713. end;
  714.  
  715. procedure TForm1.PageControl2Changing(Sender: TObject;
  716.   var AllowChange: Boolean);
  717. begin
  718.   if PageControl2.ActivePage = HelpTabsheet then
  719.     HlpPrevPageIndex := -1
  720.   else
  721.     HlpPrevPageIndex := PageControl2.ActivePageIndex;
  722.  
  723.   AllowChange := true;
  724. end;
  725.  
  726. procedure TForm1.Memo2DblClick(Sender: TObject);
  727. var
  728.   line: string;
  729.  
  730.   procedure _process(toFind: string);
  731.   var
  732.     p, lineno: integer;
  733.   begin
  734.     if FileSystemCaseSensitive then
  735.       p := Pos(toFind, line)
  736.     else
  737.       p := Pos(toFind.ToLower, line.ToLower);
  738.     if p <> 0 then
  739.     begin
  740.       line := copy(line, p+length(toFind), 99);
  741.       if not TryStrToInt(line, lineno) then exit;
  742.       GotoLineNo(lineno);
  743.     end;
  744.   end;
  745.  
  746. begin
  747.   line := memo2.Lines.Strings[Memo2.CaretPos.Y];
  748.  
  749.   {$REGION 'Possibility 1: filename.php:lineno'}
  750.   _process(ExtractFileName(GetScrapFile) + ':');
  751.   {$ENDREGION}
  752.  
  753.   {$REGION 'Possibility 2: on line xx'}
  754.   _process(ExtractFileName(GetScrapFile) + ' on line ');
  755.   {$ENDREGION}
  756. end;
  757.  
  758. procedure TForm1.Memo2KeyDown(Sender: TObject; var Key: Word;
  759.   Shift: TShiftState);
  760. begin
  761.   if ((ssCtrl in Shift) and (Key = 65)) then TMemo(Sender).SelectAll;
  762. end;
  763.  
  764. function TForm1.MarkUpLineReference(cont: string): string;
  765.  
  766.   procedure _process(toFind: string);
  767.   var
  768.     p, a, b: integer;
  769.     num: integer;
  770.     insert_a, insert_b: string;
  771.   begin
  772.     if FileSystemCaseSensitive then
  773.       p := Pos(toFind, cont)
  774.     else
  775.       p := Pos(toFind.ToLower, cont.ToLower);
  776.     while p >= 1 do
  777.     begin
  778.       a := p;
  779.       b := p + length(toFind);
  780.       num := 0;
  781.       while CharInSet(cont[b], ['0'..'9']) do
  782.       begin
  783.         num := num*10 + StrToInt(cont[b]);
  784.         inc(b);
  785.       end;
  786.  
  787.       insert_b := '</a>';
  788.       insert_a := '<a href="' + FASTPHP_GOTO_URI_PREFIX + IntToStr(num) + '">';
  789.  
  790.       insert(insert_b, cont, b);
  791.       insert(insert_a, cont, a);
  792.  
  793.       p := b + Length(insert_a) + Length(insert_b);
  794.  
  795.       p := PosEx(toFind, cont, p+1);
  796.     end;
  797.   end;
  798.  
  799. begin
  800.   {$REGION 'Possibility 1: filename.php:lineno'}
  801.   _process(ExtractFileName(GetScrapFile) + ':');
  802.   {$ENDREGION}
  803.  
  804.   {$REGION 'Possibility 2: on line xx'}
  805.   _process(ExtractFileName(GetScrapFile) + ' on line ');
  806.   {$ENDREGION}
  807.  
  808.   result := cont;
  809. end;
  810.  
  811. function TForm1.InputRequestCallback: string;
  812. begin
  813.   result := SynEdit1.Text;
  814. end;
  815.  
  816. procedure TForm1.OutputNotifyCallback(const data: string);
  817. begin
  818.   TreeView1.FillWithFastPHPData(data);
  819. end;
  820.  
  821. end.
  822.