Subversion Repositories fastphp

Rev

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