Subversion Repositories fastphp

Rev

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