Subversion Repositories fastphp

Rev

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