Subversion Repositories fastphp

Rev

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

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