Subversion Repositories fastphp

Rev

Rev 32 | Rev 36 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

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