Subversion Repositories fastphp

Rev

Rev 78 | Rev 80 | 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
 
72 daniel-mar 14
// TODO: if a scrapfile is already open, create a new scrap file (scrap2.php)
2 daniel-mar 15
// TODO: localize
16
// TODO: wieso geht copy paste im twebbrowser nicht???
49 daniel-mar 17
// TODO: Wieso dauert webbrowser1 erste kompilierung so lange???
5 daniel-mar 18
// TODO: wieso kommt syntax fehler zweimal? einmal stderr einmal stdout?
19
// TODO: Browser titlebar (link preview)
49 daniel-mar 20
// TODO: "jump to next/prev todo" buttons/shortcuts
21
// TODO: "increase/decrease indent" buttons/shortcuts
2 daniel-mar 22
 
63 daniel-mar 23
// Small things:
24
// - The scroll bars of SynEdit are not affected by the dark theme
25
 
2 daniel-mar 26
// Future ideas
31 daniel-mar 27
// - code insight
2 daniel-mar 28
// - verschiedene php versionen?
29
// - webbrowser1 nur laden, wenn man den tab anwählt?
30
// - doppelklick auf tab soll diesen schließen
5 daniel-mar 31
// - Onlinehelp (www) aufrufen
13 daniel-mar 32
// - Let all colors be adjustable
21 daniel-mar 33
// - code in bildschirmmitte (horizontal)?
72 daniel-mar 34
// - search in files of a directory
35
// - multi tab?
36
// - DDE (drag n drop)
37
// - check if file has been modified in another application
2 daniel-mar 38
 
39
interface
40
 
41
uses
27 daniel-mar 42
  // TODO: "{$IFDEF USE_SHDOCVW_TLB}_TLB{$ENDIF}" does not work with Delphi 10.2
2 daniel-mar 43
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
4 daniel-mar 44
  Dialogs, StdCtrls, OleCtrls, ComCtrls, ExtCtrls, ToolWin, IniFiles,
45 daniel-mar 45
  SynEditHighlighter, SynHighlighterPHP, SynEdit, ShDocVw_TLB, FindReplace,
46
  ActnList, SynEditMiscClasses, SynEditSearch, RunPHP, ImgList, SynUnicode,
62 daniel-mar 47
  System.ImageList, System.Actions, Vcl.Menus, SHDocVw, Vcl.Themes;
2 daniel-mar 48
 
23 daniel-mar 49
{.$DEFINE OnlineHelp}
50
 
2 daniel-mar 51
type
52
  TForm1 = class(TForm)
53
    PageControl1: TPageControl;
54
    PlaintextTabSheet: TTabSheet;
55
    HtmlTabSheet: TTabSheet;
56
    Memo2: TMemo;
57
    WebBrowser1: TWebBrowser;
58
    Splitter1: TSplitter;
59
    PageControl2: TPageControl;
20 daniel-mar 60
    CodeTabsheet: TTabSheet;
2 daniel-mar 61
    HelpTabsheet: TTabSheet;
62
    WebBrowser2: TWebBrowser;
63
    OpenDialog1: TOpenDialog;
64
    Panel1: TPanel;
65
    OpenDialog3: TOpenDialog;
4 daniel-mar 66
    SynEdit1: TSynEdit;
67
    SynPHPSyn1: TSynPHPSyn;
5 daniel-mar 68
    Panel2: TPanel;
69
    SynEditFocusTimer: TTimer;
70
    Button1: TButton;
71
    Button2: TButton;
72
    Button3: TButton;
13 daniel-mar 73
    Button4: TButton;
74
    Button5: TButton;
75
    Button6: TButton;
76
    ActionList: TActionList;
77
    ActionFind: TAction;
78
    ActionReplace: TAction;
79
    ActionFindNext: TAction;
80
    ActionGoto: TAction;
81
    ActionSave: TAction;
82
    ActionHelp: TAction;
83
    ActionRun: TAction;
84
    ActionESC: TAction;
85
    Button7: TButton;
15 daniel-mar 86
    ActionOpen: TAction;
87
    Button8: TButton;
22 daniel-mar 88
    Button9: TButton;
89
    ActionFindPrev: TAction;
23 daniel-mar 90
    Timer1: TTimer;
91
    ActionSpaceToTab: TAction;
92
    Button11: TButton;
24 daniel-mar 93
    SynEditSearch1: TSynEditSearch;
27 daniel-mar 94
    TreeView1: TTreeView;
26 daniel-mar 95
    Splitter2: TSplitter;
33 daniel-mar 96
    btnLint: TButton;
97
    ActionLint: TAction;
36 daniel-mar 98
    ImageList1: TImageList;
45 daniel-mar 99
    RunPopup: TPopupMenu;
100
    OpeninIDE1: TMenuItem;
101
    ActionRunConsole: TAction;
102
    Runinconsole1: TMenuItem;
56 daniel-mar 103
    SavePopup: TPopupMenu;
104
    Saveas1: TMenuItem;
105
    Save1: TMenuItem;
106
    SaveDialog1: TSaveDialog;
57 daniel-mar 107
    BtnSpecialChars: TImage;
108
    BtnSpecialCharsOff: TImage;
109
    BtnSpecialCharsOn: TImage;
62 daniel-mar 110
    BtnLightOn: TImage;
111
    BtnLightOff: TImage;
112
    BtnLight: TImage;
113
    StartUpTimer: TTimer;
2 daniel-mar 114
    procedure Run(Sender: TObject);
45 daniel-mar 115
    procedure RunConsole(Sender: TObject);
2 daniel-mar 116
    procedure FormShow(Sender: TObject);
117
    procedure FormCreate(Sender: TObject);
118
    procedure FormDestroy(Sender: TObject);
119
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
120
    procedure PageControl2Changing(Sender: TObject; var AllowChange: Boolean);
5 daniel-mar 121
    procedure Memo2DblClick(Sender: TObject);
45 daniel-mar 122
    (*
44 daniel-mar 123
    {$IFDEF USE_SHDOCVW_TLB}
45 daniel-mar 124
    *)
5 daniel-mar 125
    procedure WebBrowser1BeforeNavigate2(ASender: TObject;
27 daniel-mar 126
      const pDisp: IDispatch; const URL, Flags, TargetFrameName, PostData,
5 daniel-mar 127
      Headers: OleVariant; var Cancel: WordBool);
45 daniel-mar 128
    (*
44 daniel-mar 129
    {$ELSE}
130
    procedure WebBrowser1BeforeNavigate2(ASender: TObject;
131
      const pDisp: IDispatch; var URL, Flags, TargetFrameName, PostData,
132
      Headers: OleVariant; var Cancel: WordBool);
133
    {$ENDIF}
45 daniel-mar 134
    *)
44 daniel-mar 135
    procedure BeforeNavigate(const URL: OleVariant; var Cancel: WordBool);
5 daniel-mar 136
    procedure SynEditFocusTimerTimer(Sender: TObject);
13 daniel-mar 137
    procedure ActionFindExecute(Sender: TObject);
138
    procedure ActionReplaceExecute(Sender: TObject);
139
    procedure ActionFindNextExecute(Sender: TObject);
140
    procedure ActionGotoExecute(Sender: TObject);
141
    procedure ActionSaveExecute(Sender: TObject);
142
    procedure ActionHelpExecute(Sender: TObject);
143
    procedure ActionRunExecute(Sender: TObject);
144
    procedure ActionESCExecute(Sender: TObject);
145
    procedure SynEdit1MouseWheelDown(Sender: TObject; Shift: TShiftState;
146
      MousePos: TPoint; var Handled: Boolean);
147
    procedure SynEdit1MouseWheelUp(Sender: TObject; Shift: TShiftState;
148
      MousePos: TPoint; var Handled: Boolean);
15 daniel-mar 149
    procedure ActionOpenExecute(Sender: TObject);
150
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
17 daniel-mar 151
    procedure Memo2KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
22 daniel-mar 152
    procedure ActionFindPrevExecute(Sender: TObject);
23 daniel-mar 153
    procedure SynEdit1MouseCursor(Sender: TObject;
154
      const aLineCharPos: TBufferCoord; var aCursor: TCursor);
155
    procedure Timer1Timer(Sender: TObject);
156
    procedure ActionSpaceToTabExecute(Sender: TObject);
27 daniel-mar 157
    procedure TreeView1DblClick(Sender: TObject);
30 daniel-mar 158
    procedure SynEdit1GutterClick(Sender: TObject; Button: TMouseButton; X, Y,
159
      Line: Integer; Mark: TSynEditMark);
31 daniel-mar 160
    procedure SynEdit1PaintTransient(Sender: TObject; Canvas: TCanvas;
161
      TransientType: TTransientType);
33 daniel-mar 162
    procedure ActionLintExecute(Sender: TObject);
45 daniel-mar 163
    procedure ActionRunConsoleExecute(Sender: TObject);
47 daniel-mar 164
    procedure SynEdit1Change(Sender: TObject);
56 daniel-mar 165
    procedure Saveas1Click(Sender: TObject);
166
    procedure Save1Click(Sender: TObject);
57 daniel-mar 167
    procedure BtnSpecialCharsClick(Sender: TObject);
60 daniel-mar 168
    procedure WebBrowser1WindowClosing(ASender: TObject;
169
      IsChildWindow: WordBool; var Cancel: WordBool);
62 daniel-mar 170
    procedure BtnLightClick(Sender: TObject);
171
    procedure StartUpTimerTimer(Sender: TObject);
2 daniel-mar 172
  private
67 daniel-mar 173
    hMutex: THandle;
2 daniel-mar 174
    CurSearchTerm: string;
175
    HlpPrevPageIndex: integer;
24 daniel-mar 176
    SrcRep: TSynEditFindReplace;
23 daniel-mar 177
    {$IFDEF OnlineHelp}
178
    gOnlineHelpWord: string;
179
    {$ENDIF}
2 daniel-mar 180
    procedure Help;
40 daniel-mar 181
    function InputRequestCallback(var data: AnsiString): boolean;
182
    function OutputNotifyCallback(const data: AnsiString): boolean;
67 daniel-mar 183
    procedure RightTrimAll;
2 daniel-mar 184
  protected
185
    ChmIndex: TMemIniFile;
19 daniel-mar 186
    FScrapFile: string;
56 daniel-mar 187
    FSaveAsFilename: string;
27 daniel-mar 188
    codeExplorer: TRunCodeExplorer;
189
    procedure GotoLineNo(LineNo: integer);
2 daniel-mar 190
    function GetScrapFile: string;
27 daniel-mar 191
    procedure StartCodeExplorer;
47 daniel-mar 192
    procedure RefreshModifySign;
62 daniel-mar 193
    procedure Theme_Light;
194
    procedure Theme_Dark;
195
    function IsThemeDark: boolean;
67 daniel-mar 196
    function MarkUpLineReference(cont: string): string;
76 daniel-mar 197
    procedure SaveToFile(filename: string);
2 daniel-mar 198
  end;
199
 
200
var
201
  Form1: TForm1;
202
 
203
implementation
204
 
205
{$R *.dfm}
206
 
30 daniel-mar 207
{$R Cursors.res}
208
 
2 daniel-mar 209
uses
25 daniel-mar 210
  Functions, StrUtils, WebBrowserUtils, FastPHPUtils, Math, ShellAPI, RichEdit,
49 daniel-mar 211
  FastPHPTreeView, ImageListEx, FastPHPConfig;
2 daniel-mar 212
 
30 daniel-mar 213
const
214
  crMouseGutter = 1;
215
 
47 daniel-mar 216
procedure TForm1.RefreshModifySign;
217
var
218
  tmp: string;
219
begin
220
  tmp := Caption;
221
 
222
  tmp := StringReplace(tmp, '*', '', [rfReplaceAll]);
223
  if SynEdit1.Modified then tmp := tmp + '*';
224
 
225
  if Caption <> tmp then Caption := tmp;
226
end;
227
 
13 daniel-mar 228
procedure TForm1.ActionFindNextExecute(Sender: TObject);
229
begin
230
  SrcRep.FindNext;
231
end;
232
 
22 daniel-mar 233
procedure TForm1.ActionFindPrevExecute(Sender: TObject);
234
begin
235
  SrcRep.FindPrev;
236
end;
237
 
13 daniel-mar 238
procedure TForm1.ActionGotoExecute(Sender: TObject);
5 daniel-mar 239
var
240
  val: string;
241
  lineno: integer;
242
begin
13 daniel-mar 243
  // TODO: VK_LMENU does not work! only works with AltGr but not Alt
244
  // http://stackoverflow.com/questions/16828250/delphi-xe2-how-to-prevent-the-alt-key-stealing-focus ?
5 daniel-mar 245
 
13 daniel-mar 246
  InputQuery('Go to', 'Line number:', val);
247
  if not TryStrToInt(val, lineno) then
248
  begin
249
    if SynEdit1.CanFocus then SynEdit1.SetFocus;
250
    exit;
251
  end;
252
  GotoLineNo(lineno);
253
end;
5 daniel-mar 254
 
13 daniel-mar 255
procedure TForm1.ActionHelpExecute(Sender: TObject);
256
begin
257
  Help;
258
  if PageControl2.ActivePage = HelpTabsheet then
259
    WebBrowser2.SetFocus
20 daniel-mar 260
  else if PageControl2.ActivePage = CodeTabsheet then
13 daniel-mar 261
    SynEdit1.SetFocus;
262
end;
8 daniel-mar 263
 
33 daniel-mar 264
procedure TForm1.ActionLintExecute(Sender: TObject);
265
begin
266
  Run(Sender);
267
  SynEdit1.SetFocus;
268
end;
269
 
15 daniel-mar 270
procedure TForm1.ActionOpenExecute(Sender: TObject);
271
begin
272
  If OpenDialog3.Execute then
273
  begin
62 daniel-mar 274
    ShellExecute(0, 'open', PChar(ParamStr(0)), PChar('"' + OpenDialog3.FileName + '"'), '', SW_NORMAL);
15 daniel-mar 275
  end;
276
end;
277
 
13 daniel-mar 278
procedure TForm1.ActionReplaceExecute(Sender: TObject);
279
begin
280
  SrcRep.ReplaceExecute;
281
end;
5 daniel-mar 282
 
45 daniel-mar 283
procedure TForm1.ActionRunConsoleExecute(Sender: TObject);
284
begin
285
  RunConsole(Sender);
286
  SynEdit1.SetFocus;
287
end;
288
 
13 daniel-mar 289
procedure TForm1.ActionRunExecute(Sender: TObject);
290
begin
291
  Run(Sender);
292
  SynEdit1.SetFocus;
293
end;
5 daniel-mar 294
 
67 daniel-mar 295
procedure TForm1.RightTrimAll;
296
var
297
  i: integer;
298
begin
68 daniel-mar 299
  for i := 0 to SynEdit1.Lines.Count-1 do
67 daniel-mar 300
  begin
301
    SynEdit1.Lines.Strings[i] := TrimRight(SynEdit1.Lines.Strings[i]);
302
  end;
77 daniel-mar 303
 
304
  (*
305
  while (SynEdit1.Lines.Count > 0) and (SynEdit1.Lines.Strings[SynEdit1.Lines.Count-1] = '') do
306
  begin
307
    SynEdit1.Lines.Delete(SynEdit1.Lines.Count-1);
308
  end;
309
  if SynEdit1.SelStart > Length(SynEdit1.Text)-1 then
310
  begin
311
    // TODO: This code does not work...
312
    SynEdit1.SelStart := Length(SynEdit1.Text)-1;
313
    SynEdit1.SelEnd   := Length(SynEdit1.Text)-1;
314
  end;
315
  *)
67 daniel-mar 316
end;
317
 
13 daniel-mar 318
procedure TForm1.ActionSaveExecute(Sender: TObject);
319
begin
67 daniel-mar 320
  RightTrimAll;
76 daniel-mar 321
  SaveToFile(GetScrapFile);
16 daniel-mar 322
  SynEdit1.Modified := false;
47 daniel-mar 323
  RefreshModifySign;
68 daniel-mar 324
  if SynEdit1.CanFocus then SynEdit1.SetFocus;
13 daniel-mar 325
end;
326
 
23 daniel-mar 327
procedure TForm1.ActionSpaceToTabExecute(Sender: TObject);
328
 
329
    function SpacesAtBeginning(line: string): integer;
330
    begin
331
      result := 0;
44 daniel-mar 332
      if Trim(line) = '' then exit;
23 daniel-mar 333
      while line[result+1] = ' ' do
334
      begin
335
        inc(result);
336
      end;
337
    end;
338
 
44 daniel-mar 339
    function GuessIndent(lines: {$IFDEF UNICODE}TStrings{$ELSE}TUnicodeStrings{$ENDIF}): integer;
23 daniel-mar 340
      function _Check(indent: integer): boolean;
341
      var
342
        i: integer;
343
      begin
344
        result := true;
345
        for i := 0 to lines.Count-1 do
346
          if SpacesAtBeginning(lines.Strings[i]) mod indent <> 0 then
347
          begin
348
            // ShowMessageFmt('Zeile "%s" nicht durch %d teilbar!', [lines.strings[i], indent]);
44 daniel-mar 349
            result := false;
350
            exit;
23 daniel-mar 351
          end;
352
      end;
353
    var
354
      i: integer;
355
    begin
356
      for i := 8 downto 2 do
357
      begin
44 daniel-mar 358
        if _Check(i) then
359
        begin
360
          result := i;
361
          exit;
362
        end;
23 daniel-mar 363
      end;
364
      result := -1;
365
    end;
366
 
44 daniel-mar 367
    procedure SpaceToTab(lines: {$IFDEF UNICODE}TStrings{$ELSE}TUnicodeStrings{$ENDIF}; indent: integer);
23 daniel-mar 368
    var
369
      i, spaces: integer;
370
    begin
371
      for i := 0 to lines.Count-1 do
372
      begin
373
        spaces := SpacesAtBeginning(lines.Strings[i]);
374
        lines.Strings[i] := StringOfChar(#9, spaces div indent) + StringOfChar(' ', spaces mod indent) + Copy(lines.Strings[i], spaces+1, Length(lines.Strings[i])-spaces);
375
      end;
376
    end;
377
 
44 daniel-mar 378
    function SpacesAvailable(lines: {$IFDEF UNICODE}TStrings{$ELSE}TUnicodeStrings{$ENDIF}): boolean;
23 daniel-mar 379
    var
380
      i, spaces: integer;
381
    begin
382
      for i := 0 to lines.Count-1 do
383
      begin
384
        spaces := SpacesAtBeginning(lines.Strings[i]);
44 daniel-mar 385
        if spaces > 0 then
386
        begin
387
          result := true;
388
          exit;
389
        end;
23 daniel-mar 390
      end;
44 daniel-mar 391
      result := false;
392
      exit;
23 daniel-mar 393
    end;
394
 
395
var
396
  val: string;
397
  ind: integer;
398
resourcestring
399
  SNoLinesAvailable = 'No lines with spaces at the beginning available';
400
begin
401
  // TODO: if something is selected, only process the selected part
402
 
403
  if not SpacesAvailable(SynEdit1.Lines) then
404
  begin
49 daniel-mar 405
    MessageDlg(SNoLinesAvailable, mtInformation, [mbOk], 0);
23 daniel-mar 406
    exit;
407
  end;
408
 
409
  ind := GuessIndent(SynEdit1.Lines);
410
  if ind <> -1 then val := IntToStr(ind);
411
 
412
  InputQuery('Spaces to tabs', 'Indent:', val); // TODO: handle CANCEL correctly...
44 daniel-mar 413
  if TryStrToInt(Trim(val), ind) then
23 daniel-mar 414
  begin
415
    if ind = 0 then exit;
416
    SpaceToTab(SynEdit1.Lines, ind);
417
  end;
418
 
419
  if SynEdit1.CanFocus then SynEdit1.SetFocus;
420
end;
421
 
13 daniel-mar 422
procedure TForm1.ActionESCExecute(Sender: TObject);
423
begin
424
  if (HlpPrevPageIndex <> -1) and (PageControl2.ActivePage = HelpTabSheet) and
425
     (HelpTabsheet.TabVisible) then
426
  begin
427
    PageControl2.ActivePageIndex := HlpPrevPageIndex;
428
    HelpTabsheet.TabVisible := false;
2 daniel-mar 429
  end;
13 daniel-mar 430
 
431
  // Dirty hack...
22 daniel-mar 432
  SrcRep.CloseDialogs;
2 daniel-mar 433
end;
434
 
13 daniel-mar 435
procedure TForm1.ActionFindExecute(Sender: TObject);
436
begin
437
  SrcRep.FindExecute;
438
end;
439
 
16 daniel-mar 440
var
441
  firstTimeBrowserLoad: boolean = true;
2 daniel-mar 442
procedure TForm1.Run(Sender: TObject);
16 daniel-mar 443
var
444
  bakTS: TTabSheet;
67 daniel-mar 445
  //ss: TStringStream;
446
  //bakPos: Int64;
2 daniel-mar 447
begin
5 daniel-mar 448
  memo2.Lines.Text := '';
16 daniel-mar 449
 
450
  if firstTimeBrowserLoad then
451
  begin
452
    bakTS := PageControl1.ActivePage;
453
    try
454
      PageControl1.ActivePage := HtmlTabSheet; // Required for the first time, otherwise, WebBrowser1.Clear will hang
455
      Webbrowser1.Clear;
456
    finally
457
      PageControl1.ActivePage := bakTS;
458
    end;
459
    firstTimeBrowserLoad := false;
460
  end
461
  else
462
    Webbrowser1.Clear;
463
 
5 daniel-mar 464
  Screen.Cursor := crHourGlass;
465
  Application.ProcessMessages;
466
 
467
  try
47 daniel-mar 468
    ActionSave.Execute; // TODO: if it is not the scrap file: do not save the file, since the user did not intended to save... better create a temporary file and run it instead.
5 daniel-mar 469
 
62 daniel-mar 470
    // TODO 70421 * <fastphp> flush() mittels ContentCallBack implementieren... ich möchte bei langen scripts statusanzeigen realisieren können mit javascript das stück für stück geladen wird !!!!!!!!
471
    // TODO 70422 * <fastphp> wenn ein script hängt, soll man es abwürgen dürfen!!!!!!
45 daniel-mar 472
    memo2.Lines.Text := RunPHPScript(GetScrapFile, Sender=ActionLint, False);
5 daniel-mar 473
 
72 daniel-mar 474
    {$REGION 'Show in Web Browser'}
475
    Webbrowser1.LoadHTML(MarkUpLineReference(memo2.Lines.Text), GetScrapFile);
5 daniel-mar 476
 
62 daniel-mar 477
    // Alternatively:
478
    (*
479
    ss := TstringStream.Create;
480
    ss.WriteString(MarkUpLineReference(memo2.Lines.Text));
481
    ss.Position := 0;
482
    Webbrowser1.LoadStream(ss, GetScrapFile);
483
    Webbrowser1.Wait;
484
    ss.Free;
485
    *)
72 daniel-mar 486
    {$ENDREGION}
62 daniel-mar 487
 
5 daniel-mar 488
    if IsTextHTML(memo2.lines.text) then
489
      PageControl1.ActivePage := HtmlTabSheet
490
    else
491
      PageControl1.ActivePage := PlaintextTabSheet;
492
  finally
493
    Screen.Cursor := crDefault;
2 daniel-mar 494
  end;
5 daniel-mar 495
end;
2 daniel-mar 496
 
45 daniel-mar 497
procedure TForm1.RunConsole(Sender: TObject);
498
begin
47 daniel-mar 499
  ActionSave.Execute; // TODO: if it is not the scrap file: do not save the file, since the user did not intended to save... better create a temporary file and run it instead.
45 daniel-mar 500
  RunPHPScript(GetScrapFile, Sender=ActionLint, True);
501
end;
502
 
47 daniel-mar 503
procedure TForm1.SynEdit1Change(Sender: TObject);
504
begin
505
  RefreshModifySign;
506
end;
507
 
30 daniel-mar 508
procedure TForm1.SynEdit1GutterClick(Sender: TObject; Button: TMouseButton; X,
509
  Y, Line: Integer; Mark: TSynEditMark);
510
begin
511
  (*
512
  TSynEdit(Sender).CaretX := 1;
513
  TSynEdit(Sender).CaretY := Line;
514
  TSynEdit(Sender).SelLength := Length(TSynEdit(Sender).LineText);
515
  *)
516
end;
517
 
23 daniel-mar 518
procedure TForm1.SynEdit1MouseCursor(Sender: TObject; const aLineCharPos: TBufferCoord; var aCursor: TCursor);
519
{$IFDEF OnlineHelp}
520
var
521
  Line: Integer;
522
  Column: Integer;
523
  word: string;
524
begin
525
  Line  := aLineCharPos.Line-1;
526
  Column := aLineCharPos.Char-1;
527
  word := GetWordUnderPos(TSynEdit(Sender), Line, Column);
528
  if word <> gOnlineHelpWord then
529
  begin
530
    gOnlineHelpWord := word;
531
    Timer1.Enabled := false;
532
    Timer1.Enabled := true;
533
  end;
534
{$ELSE}
535
begin
536
{$ENDIF}
537
end;
538
 
13 daniel-mar 539
procedure TForm1.SynEdit1MouseWheelDown(Sender: TObject; Shift: TShiftState;
540
  MousePos: TPoint; var Handled: Boolean);
541
begin
542
  if ssCtrl in Shift then
543
  begin
544
    SynEdit1.Font.Size := Max(SynEdit1.Font.Size - 1, 5);
23 daniel-mar 545
    Handled := true;
546
  end
547
  else Handled := false;
13 daniel-mar 548
end;
549
 
550
procedure TForm1.SynEdit1MouseWheelUp(Sender: TObject; Shift: TShiftState;
551
  MousePos: TPoint; var Handled: Boolean);
552
begin
553
  if ssCtrl in Shift then
554
  begin
555
    SynEdit1.Font.Size := SynEdit1.Font.Size + 1;
23 daniel-mar 556
    Handled := true;
557
  end
558
  else Handled := false;
13 daniel-mar 559
end;
560
 
31 daniel-mar 561
procedure TForm1.SynEdit1PaintTransient(Sender: TObject; Canvas: TCanvas; TransientType: TTransientType);
562
var
563
  Editor: TSynEdit;
564
  OpenChars: array of WideChar;//[0..2] of WideChar=();
565
  CloseChars: array of WideChar;//[0..2] of WideChar=();
566
 
567
  function IsCharBracket(AChar: WideChar): Boolean;
568
  begin
569
    case AChar of
570
      '{','[','(','<','}',']',')','>':
571
        Result := True;
572
      else
573
        Result := False;
574
    end;
575
  end;
576
 
577
  function CharToPixels(P: TBufferCoord): TPoint;
578
  begin
579
    Result := Editor.RowColumnToPixels(Editor.BufferToDisplayPos(P));
580
  end;
581
 
582
var
63 daniel-mar 583
  COLOR_FG: TColor;
584
  COLOR_BG: TColor;
31 daniel-mar 585
  P: TBufferCoord;
586
  Pix: TPoint;
587
  D: TDisplayCoord;
588
  S: UnicodeString;
589
  I: Integer;
590
  Attri: TSynHighlighterAttributes;
591
  ArrayLength: Integer;
592
  start: Integer;
593
  TmpCharA, TmpCharB: WideChar;
63 daniel-mar 594
begin
31 daniel-mar 595
  // Source: https://github.com/SynEdit/SynEdit/blob/master/Demos/OnPaintTransientDemo/Unit1.pas
596
 
63 daniel-mar 597
  if IsThemeDark then
598
  begin
599
    COLOR_FG := clLime;
600
    COLOR_BG := clGreen;
601
  end
602
  else
603
  begin
604
    COLOR_FG := clGreen;
605
    COLOR_BG := clLime;
606
  end;
607
 
31 daniel-mar 608
  if TSynEdit(Sender).SelAvail then exit;
609
  Editor := TSynEdit(Sender);
610
  ArrayLength:= 3;
611
 
612
  (*
613
  if (Editor.Highlighter = shHTML) or (Editor.Highlighter = shXML) then
614
    inc(ArrayLength);
615
  *)
616
 
617
  SetLength(OpenChars, ArrayLength);
618
  SetLength(CloseChars, ArrayLength);
619
  for i := 0 to ArrayLength - 1 do
620
  begin
621
    case i of
622
      0: begin OpenChars[i] := '('; CloseChars[i] := ')'; end;
623
      1: begin OpenChars[i] := '{'; CloseChars[i] := '}'; end;
624
      2: begin OpenChars[i] := '['; CloseChars[i] := ']'; end;
625
      3: begin OpenChars[i] := '<'; CloseChars[i] := '>'; end;
626
    end;
627
  end;
628
 
629
  P := Editor.CaretXY;
630
  D := Editor.DisplayXY;
631
 
632
  Start := Editor.SelStart;
633
 
634
  if (Start > 0) and (Start <= length(Editor.Text)) then
635
    TmpCharA := Editor.Text[Start]
636
  else
637
    TmpCharA := #0;
638
 
44 daniel-mar 639
  if (Start > 0){Added by VTS} and (Start < length(Editor.Text)) then
31 daniel-mar 640
    TmpCharB := Editor.Text[Start + 1]
641
  else
642
    TmpCharB := #0;
643
 
644
  if not IsCharBracket(TmpCharA) and not IsCharBracket(TmpCharB) then exit;
645
  S := TmpCharB;
646
  if not IsCharBracket(TmpCharB) then
647
  begin
648
    P.Char := P.Char - 1;
649
    S := TmpCharA;
650
  end;
651
  Editor.GetHighlighterAttriAtRowCol(P, S, Attri);
652
 
653
  if (Editor.Highlighter.SymbolAttribute = Attri) then
654
  begin
655
    for i := low(OpenChars) to High(OpenChars) do
656
    begin
657
      if (S = OpenChars[i]) or (S = CloseChars[i]) then
658
      begin
659
        Pix := CharToPixels(P);
660
 
661
        Editor.Canvas.Brush.Style := bsSolid;//Clear;
662
        Editor.Canvas.Font.Assign(Editor.Font);
663
        Editor.Canvas.Font.Style := Attri.Style;
664
 
665
        if (TransientType = ttAfter) then
666
        begin
667
          Editor.Canvas.Font.Color := COLOR_FG;
668
          Editor.Canvas.Brush.Color := COLOR_BG;
669
        end
670
        else
671
        begin
672
          Editor.Canvas.Font.Color := Attri.Foreground;
673
          Editor.Canvas.Brush.Color := Attri.Background;
674
        end;
675
        if Editor.Canvas.Font.Color = clNone then
676
          Editor.Canvas.Font.Color := Editor.Font.Color;
677
        if Editor.Canvas.Brush.Color = clNone then
678
          Editor.Canvas.Brush.Color := Editor.Color;
679
 
680
        Editor.Canvas.TextOut(Pix.X, Pix.Y, S);
681
        P := Editor.GetMatchingBracketEx(P);
682
 
683
        if (P.Char > 0) and (P.Line > 0) then
684
        begin
685
          Pix := CharToPixels(P);
686
          if Pix.X > Editor.Gutter.Width then
687
          begin
688
            {$REGION 'Added by ViaThinkSoft'}
689
            if (TransientType = ttAfter) then
690
            begin
691
              Editor.Canvas.Font.Color := COLOR_FG;
692
              Editor.Canvas.Brush.Color := COLOR_BG;
693
            end
694
            else
695
            begin
696
              Editor.Canvas.Font.Color := Attri.Foreground;
697
              Editor.Canvas.Brush.Color := Attri.Background;
698
            end;
699
            if Editor.Canvas.Font.Color = clNone then
700
              Editor.Canvas.Font.Color := Editor.Font.Color;
701
            if Editor.Canvas.Brush.Color = clNone then
702
              Editor.Canvas.Brush.Color := Editor.Color;
703
            {$ENDREGION}
704
            if S = OpenChars[i] then
705
              Editor.Canvas.TextOut(Pix.X, Pix.Y, CloseChars[i])
706
            else Editor.Canvas.TextOut(Pix.X, Pix.Y, OpenChars[i]);
707
          end;
708
        end;
709
      end;
710
    end;
711
    Editor.Canvas.Brush.Style := bsSolid;
712
  end;
713
end;
714
 
5 daniel-mar 715
procedure TForm1.SynEditFocusTimerTimer(Sender: TObject);
716
begin
717
  SynEditFocusTimer.Enabled := false;
718
  Button1.SetFocus; // Workaround for weird bug... This (and the timer) is necessary to get the focus to SynEdit1
719
  SynEdit1.SetFocus;
720
end;
2 daniel-mar 721
 
62 daniel-mar 722
procedure TForm1.Theme_Dark;
723
begin
724
  if IsThemeDark then exit;
725
  TStyleManager.TrySetStyle('Windows10 SlateGray');
726
  Color := 1316887;
727
  Font.Color := clCream;
728
  //Memo2.Font.Color := clCream;
729
  //Memo2.ParentColor := true;
730
  SynEdit1.ActiveLineColor := 2238502;
731
  SynEdit1.Color := 1316887;
732
  SynEdit1.Font.Color := clCream;
733
  SynEdit1.Gutter.Color := 1316887;
734
  SynEdit1.Gutter.Font.Color := clCream;
735
  SynEdit1.Gutter.GradientStartColor := 2238502;
736
  SynEdit1.Gutter.GradientEndColor := 1316887;
64 daniel-mar 737
  SynPHPSyn1.CommentAttri.Foreground := $00837B82;
62 daniel-mar 738
  SynPHPSyn1.IdentifierAttri.Foreground := 9627120;
739
  SynPHPSyn1.KeyAttri.Foreground := 4157595;
740
  SynPHPSyn1.NumberAttri.Foreground := 5008079;
741
  SynPHPSyn1.StringAttri.Foreground := 6987151;
742
  SynPHPSyn1.SymbolAttri.Foreground := 8769754;
743
  SynPHPSyn1.VariableAttri.Foreground := 6924493;
744
end;
745
 
746
procedure TForm1.Theme_Light;
747
begin
748
  if not IsThemeDark then exit;
749
  TStyleManager.TrySetStyle('Windows');
750
  Color := clBtnFace;
751
  Font.Color := clWindowText;
752
  //Memo2.Font.Color := clWindowText;
753
  SynEdit1.ActiveLineColor := 14680010;
754
  SynEdit1.Color := clWindow;
755
  SynEdit1.Font.Color := clWindowText;
756
  SynEdit1.Gutter.Color := clBtnFace;
757
  SynEdit1.Gutter.Font.Color := clWindowText;
758
  SynEdit1.Gutter.GradientStartcolor := cl3dLight;
759
  SynEdit1.Gutter.GradientEndColor := clBtnFace;;
760
  SynPHPSyn1.CommentAttri.Foreground := 33023;
761
  SynPHPSyn1.IdentifierAttri.Foreground := 4194304;
762
  SynPHPSyn1.KeyAttri.Foreground := 4227072;
763
  SynPHPSyn1.NumberAttri.Foreground := 213;
764
  SynPHPSyn1.StringAttri.Foreground := 13762560;
765
  SynPHPSyn1.SymbolAttri.Foreground := 4227072;
766
  SynPHPSyn1.VariableAttri.Foreground := 213;
767
end;
768
 
23 daniel-mar 769
procedure TForm1.Timer1Timer(Sender: TObject);
770
begin
771
  {$IFDEF OnlineHelp}
772
  Timer1.Enabled := false;
773
 
774
  // TODO: Insert a small online help hint
775
  //Caption := gOnlineHelpWord;
776
  {$ENDIF}
777
end;
778
 
27 daniel-mar 779
procedure TForm1.TreeView1DblClick(Sender: TObject);
780
var
781
  tn: TTreeNode;
32 daniel-mar 782
  lineNo: integer;
27 daniel-mar 783
begin
784
  tn := TTreeView(Sender).Selected;
785
  if tn = nil then exit;
32 daniel-mar 786
  lineNo := Integer(tn.Data);
787
  if lineNo > 0 then GotoLineNo(lineNo);
27 daniel-mar 788
end;
789
 
45 daniel-mar 790
(*
44 daniel-mar 791
{$IFDEF USE_SHDOCVW_TLB}
45 daniel-mar 792
*)
5 daniel-mar 793
procedure TForm1.WebBrowser1BeforeNavigate2(ASender: TObject;
27 daniel-mar 794
  const pDisp: IDispatch; const URL, Flags, TargetFrameName, PostData,
5 daniel-mar 795
  Headers: OleVariant; var Cancel: WordBool);
44 daniel-mar 796
begin
797
  BeforeNavigate(URL, Cancel);
798
end;
45 daniel-mar 799
(*
44 daniel-mar 800
{$ELSE}
801
procedure TForm1.WebBrowser1BeforeNavigate2(ASender: TObject;
802
  const pDisp: IDispatch; var URL, Flags, TargetFrameName, PostData,
803
  Headers: OleVariant; var Cancel: WordBool);
804
begin
805
  BeforeNavigate(URL, Cancel);
806
end;
807
{$ENDIF}
45 daniel-mar 808
*)
44 daniel-mar 809
 
67 daniel-mar 810
procedure TForm1.WebBrowser1WindowClosing(ASender: TObject;
811
  IsChildWindow: WordBool; var Cancel: WordBool);
812
resourcestring
813
  LNG_CLOSE_REQUEST = 'A script has requested the window to be closed. The window of a standalone script would now close.';
814
begin
815
  ShowMessage(LNG_CLOSE_REQUEST);
816
  TWebBrowser(ASender).Clear;
817
  Cancel := true;
818
end;
819
 
44 daniel-mar 820
procedure TForm1.BeforeNavigate(const URL: OleVariant; var Cancel: WordBool);
5 daniel-mar 821
var
8 daniel-mar 822
  s, myURL: string;
5 daniel-mar 823
  lineno: integer;
7 daniel-mar 824
  p: integer;
5 daniel-mar 825
begin
7 daniel-mar 826
  {$REGION 'Line number references (PHP errors and warnings)'}
8 daniel-mar 827
  if Copy(URL, 1, length(FASTPHP_GOTO_URI_PREFIX)) = FASTPHP_GOTO_URI_PREFIX then
5 daniel-mar 828
  begin
829
    try
8 daniel-mar 830
      s := copy(URL, length(FASTPHP_GOTO_URI_PREFIX)+1, 99);
5 daniel-mar 831
      if not TryStrToInt(s, lineno) then exit;
832
      GotoLineNo(lineno);
833
      SynEditFocusTimer.Enabled := true;
834
    finally
835
      Cancel := true;
836
    end;
8 daniel-mar 837
    Exit;
5 daniel-mar 838
  end;
7 daniel-mar 839
  {$ENDREGION}
840
 
62 daniel-mar 841
  {$REGION 'Intelligent browser (executes PHP scripts which are clicked in a hyperlink)'}
7 daniel-mar 842
  if URL <> 'about:blank' then
843
  begin
844
    myUrl := URL;
845
 
8 daniel-mar 846
    p := Pos('?', myUrl);
847
    if p >= 1 then myURL := copy(myURL, 1, p-1);
7 daniel-mar 848
 
8 daniel-mar 849
    // TODO: myURL urldecode
850
    // TODO: maybe we could even open that file in the editor!
62 daniel-mar 851
    // TODO: ?parameter=....
7 daniel-mar 852
 
8 daniel-mar 853
    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 854
    begin
27 daniel-mar 855
      WebBrowser1.LoadHTML(RunPHPScript(myURL), myUrl);
7 daniel-mar 856
      Cancel := true;
857
    end;
858
  end;
859
  {$ENDREGION}
5 daniel-mar 860
end;
2 daniel-mar 861
 
62 daniel-mar 862
procedure TForm1.BtnLightClick(Sender: TObject);
66 daniel-mar 863
var
864
  CanClose: boolean;
62 daniel-mar 865
begin
66 daniel-mar 866
  FormCloseQuery(Form1, CanClose);
867
  if not CanClose then exit;
868
 
62 daniel-mar 869
  if IsThemeDark then
870
  begin
871
    BtnLight.Picture.Assign(BtnLightOn.Picture);
872
    Theme_Light;
873
    TFastPHPConfig.DarkTheme := false;
874
  end
875
  else
876
  begin
877
    BtnLight.Picture.Assign(BtnLightOff.Picture);
878
    Theme_Dark;
879
    TFastPHPConfig.DarkTheme := true;
880
  end;
881
end;
882
 
57 daniel-mar 883
procedure TForm1.BtnSpecialCharsClick(Sender: TObject);
884
var
885
  opts: TSynEditorOptions;
886
begin
887
  opts := SynEdit1.Options;
888
  if eoShowSpecialChars in SynEdit1.Options then
889
  begin
890
    BtnSpecialChars.Picture.Assign(BtnSpecialCharsOff.Picture);
891
    Exclude(opts, eoShowSpecialChars);
892
    TFastPHPConfig.SpecialChars := false;
893
  end
894
  else
895
  begin
896
    BtnSpecialChars.Picture.Assign(BtnSpecialCharsOn.Picture);
897
    Include(opts, eoShowSpecialChars);
898
    TFastPHPConfig.SpecialChars := true;
899
  end;
900
  SynEdit1.Options := opts;
901
end;
902
 
2 daniel-mar 903
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
904
begin
49 daniel-mar 905
  TFastPHPConfig.FontSize := SynEdit1.Font.Size;
2 daniel-mar 906
end;
907
 
15 daniel-mar 908
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
909
var
910
  r: integer;
911
begin
912
  if SynEdit1.Modified then
913
  begin
56 daniel-mar 914
    if (ParamStr(1) <> '') or (FSaveAsFilename <> '') then
15 daniel-mar 915
    begin
916
      r := MessageDlg('Do you want to save?', mtConfirmation, mbYesNoCancel, 0);
917
      if r = mrCancel then
918
      begin
919
        CanClose := false;
920
        Exit;
921
      end
922
      else if r = mrYes then
923
      begin
47 daniel-mar 924
        ActionSave.Execute;
15 daniel-mar 925
        CanClose := true;
926
      end;
927
    end
928
    else
929
    begin
47 daniel-mar 930
      ActionSave.Execute;
15 daniel-mar 931
      CanClose := true;
932
    end;
933
  end;
934
end;
935
 
2 daniel-mar 936
procedure TForm1.FormCreate(Sender: TObject);
44 daniel-mar 937
var
938
  exeDir: string;
2 daniel-mar 939
begin
940
  HlpPrevPageIndex := -1;
941
  CurSearchTerm := '';
13 daniel-mar 942
  Caption := Caption + ' - ' + GetScrapFile;
24 daniel-mar 943
  SrcRep := TSynEditFindReplace.Create(self);
13 daniel-mar 944
  SrcRep.Editor := SynEdit1;
29 daniel-mar 945
  SynEdit1.Gutter.Gradient := HighColorWindows;
30 daniel-mar 946
 
947
  Screen.Cursors[crMouseGutter] := LoadCursor(hInstance, 'MOUSEGUTTER');
948
  SynEdit1.Gutter.Cursor := crMouseGutter;
36 daniel-mar 949
 
44 daniel-mar 950
  exeDir := IncludeTrailingPathDelimiter(ExtractFilePath(ParamStr(0)));
951
  if FileExists(exeDir + 'codeexplorer.bmp') then ImageList1.LoadAndSplitImages(exeDir + 'codeexplorer.bmp');
2 daniel-mar 952
end;
953
 
954
procedure TForm1.FormDestroy(Sender: TObject);
955
begin
956
  if Assigned(ChmIndex) then
957
  begin
958
    FreeAndNil(ChmIndex);
959
  end;
13 daniel-mar 960
  FreeAndNil(SrcRep);
27 daniel-mar 961
 
75 daniel-mar 962
  if hMutex <> 0 then CloseHandle(hMutex); // Note: ReleaseMutex does not work as expected!
67 daniel-mar 963
 
27 daniel-mar 964
  if Assigned(codeExplorer) then
965
  begin
966
    codeExplorer.Terminate;
967
    codeExplorer.WaitFor;
968
    FreeAndNil(codeExplorer);
969
  end;
2 daniel-mar 970
end;
971
 
71 daniel-mar 972
var
973
  FormShowRanOnce: boolean;
2 daniel-mar 974
procedure TForm1.FormShow(Sender: TObject);
975
var
976
  ScrapFile: string;
49 daniel-mar 977
  tmpFontSize: integer;
57 daniel-mar 978
  opts: TSynEditorOptions;
2 daniel-mar 979
begin
71 daniel-mar 980
  if FormShowRanOnce then exit; // If the theme is changed from normal to dark, OnShow will be called another time
981
  FormShowRanOnce := true;
982
 
2 daniel-mar 983
  ScrapFile := GetScrapFile;
984
  if ScrapFile = '' then
985
  begin
10 daniel-mar 986
    Application.Terminate; // Close;
2 daniel-mar 987
    exit;
988
  end;
57 daniel-mar 989
 
990
  opts := SynEdit1.Options;
991
  if TFastPHPConfig.SpecialChars then
992
  begin
993
    BtnSpecialChars.Picture.Assign(BtnSpecialCharsOn.Picture);
994
    Include(opts, eoShowSpecialChars);
995
  end
996
  else
997
  begin
998
    BtnSpecialChars.Picture.Assign(BtnSpecialCharsOff.Picture);
999
    Exclude(opts, eoShowSpecialChars);
1000
  end;
1001
  SynEdit1.Options := opts;
1002
 
15 daniel-mar 1003
  if FileExists(ScrapFile) then
67 daniel-mar 1004
  begin
71 daniel-mar 1005
    if hMutex = 0 then
67 daniel-mar 1006
    begin
75 daniel-mar 1007
      hMutex := CreateMutex(nil, True, PChar('FastPHP'+md5(UpperCase(ScrapFile))));
69 daniel-mar 1008
      if GetLastError = ERROR_ALREADY_EXISTS then
1009
      begin
1010
        // TODO: It would be great if the window of that FastPHP instance would switched to foreground
1011
        ShowMessageFmt('File "%s" is alrady open!', [ScrapFile]);
1012
        Close;
1013
      end;
1014
 
1015
      SynEdit1.Lines.LoadFromFile(ScrapFile);
67 daniel-mar 1016
    end;
1017
  end
15 daniel-mar 1018
  else
1019
    SynEdit1.Lines.Clear;
2 daniel-mar 1020
 
1021
  PageControl1.ActivePage := PlaintextTabSheet;
1022
 
20 daniel-mar 1023
  PageControl2.ActivePage := CodeTabsheet;
2 daniel-mar 1024
  HelpTabsheet.TabVisible := false;
5 daniel-mar 1025
 
49 daniel-mar 1026
  tmpFontSize := TFastPHPConfig.FontSize;
1027
  if tmpFontSize <> -1 then SynEdit1.Font.Size := tmpFontSize;
5 daniel-mar 1028
  SynEdit1.SetFocus;
27 daniel-mar 1029
 
1030
  DoubleBuffered := true;
1031
  StartCodeExplorer;
62 daniel-mar 1032
 
1033
  StartupTimer.Enabled := true;
2 daniel-mar 1034
end;
1035
 
56 daniel-mar 1036
procedure TForm1.Save1Click(Sender: TObject);
1037
begin
1038
  Button7.Click;
1039
end;
1040
 
1041
procedure TForm1.Saveas1Click(Sender: TObject);
75 daniel-mar 1042
var
1043
  hMutexNew: THandle;
56 daniel-mar 1044
begin
1045
  if SaveDialog1.Execute then
1046
  begin
75 daniel-mar 1047
    {$REGION 'Switch mutex'}
1048
    hMutexNew := CreateMutex(nil, True, PChar('FastPHP'+md5(UpperCase(SaveDialog1.FileName))));
1049
    if GetLastError = ERROR_ALREADY_EXISTS then
1050
    begin
1051
      ShowMessageFmt('Cannot save because file "%s", because it is alrady open in another FastPHP window!', [SaveDialog1.FileName]);
1052
      Close;
1053
    end;
1054
 
1055
    if hMutex <> 0 then CloseHandle(hMutex); // Note: ReleaseMutex does not work as expected!
1056
    hMutex := hMutexNew;
1057
    {$ENDREGION}
1058
 
56 daniel-mar 1059
    FSaveAsFilename := SaveDialog1.FileName;
1060
    Caption := Copy(Caption, 1, Pos(' - ', Caption)-1) + ' - ' + FSaveAsFilename;
1061
    Button7.Click;
1062
  end;
1063
end;
1064
 
76 daniel-mar 1065
procedure TForm1.SaveToFile(filename: string);
1066
var
79 daniel-mar 1067
  ss: TStringStream;
78 daniel-mar 1068
  ms: TMemoryStream;
1069
  fs: TFileStream;
79 daniel-mar 1070
  eolStyle: string;
1071
  str: string;
76 daniel-mar 1072
begin
78 daniel-mar 1073
  ms := TMemoryStream.Create;
79 daniel-mar 1074
  ss := TStringStream.Create('');
78 daniel-mar 1075
  fs := TFileStream.Create(filename, fmCreate);
76 daniel-mar 1076
  try
79 daniel-mar 1077
    // Save everything in a memory stream and then to a string
1078
    // in comparison to "str := SynEdit1.Lines.Text;",
1079
    // This approach should preserve LF / CRLF line endings
78 daniel-mar 1080
    SynEdit1.Lines.SaveToStream(ms);
79 daniel-mar 1081
    ms.Position := 0;
1082
    ss.CopyFrom(ms, ms.Size);
1083
    ss.Position := 0;
1084
    str := ss.ReadString(ss.Size);
1085
    ss.Size := 0; // clear string-stream, because we need it later again
77 daniel-mar 1086
 
79 daniel-mar 1087
    // Detect current line-endings
1088
    if Copy(str, 1, 2) = '#!' then
77 daniel-mar 1089
    begin
79 daniel-mar 1090
      // Shebang. Use ONLY Linux LF
1091
      str := StringReplace(str, #13#10, #10, [rfReplaceAll]);
1092
      eolStyle := #10 // Linux LF
1093
    end
1094
    else
1095
    begin
1096
      if Pos(#13#10, str) > 0 then
1097
        eolStyle := #13#10 // Windows CRLF
1098
      else if Pos(#10, str) > 0 then
1099
        eolStyle := #10 // Linux LF
78 daniel-mar 1100
      else
1101
      begin
79 daniel-mar 1102
        if DefaultTextLineBreakStyle = tlbsLF then
1103
          eolStyle := #10 // Linux LF
1104
        else if DefaultTextLineBreakStyle = tlbsCRLF then
1105
          eolStyle := #13#10 // Windows CRLF
1106
        //else if DefaultTextLineBreakStyle = tlbsCR then
1107
        //  eolStyle := #13 // Old Mac CR
1108
        else
1109
          eolStyle := #13#10; // (Should not happen)
78 daniel-mar 1110
      end;
77 daniel-mar 1111
    end;
1112
 
79 daniel-mar 1113
    // Unitfy line-endings
1114
    str := StringReplace(str, #13#10, eolStyle, [rfReplaceAll]);
1115
    str := StringReplace(str, #10, eolStyle, [rfReplaceAll]);
1116
    str := StringReplace(str, #13, '', [rfReplaceAll]);
1117
 
1118
    // Replace all trailing linebreaks by a single line break
1119
    // Note: Removing all line breaks is not good, since Linux's "nano" will
1120
    //       re-add a linebreak at the end of the file
1121
    str := TrimRight(str) + eolStyle;
1122
 
78 daniel-mar 1123
    // Old versions of Delphi/SynEdit write an UTF-8 BOM, which makes problems
1124
    // e.g. with AJAX handlers (because AJAX reponses must not have a BOM).
1125
    // So we try to avoid that.
1126
    // Note that the output is still UTF-8 encoded if the input file was UTF-8 encoded
79 daniel-mar 1127
    if Copy(str,1,3) = #$EF#$BB#$BF then Delete(str, 1, 3);
77 daniel-mar 1128
 
78 daniel-mar 1129
    // Now save to the file
79 daniel-mar 1130
    ss.WriteString(str);
1131
    ss.Position := 0;
1132
    fs.CopyFrom(ss, ss.Size-ss.Position);
76 daniel-mar 1133
  finally
78 daniel-mar 1134
    FreeAndNil(ms);
79 daniel-mar 1135
    FreeAndNil(ss);
78 daniel-mar 1136
    FreeAndNil(fs);
76 daniel-mar 1137
  end;
1138
end;
1139
 
27 daniel-mar 1140
procedure TForm1.StartCodeExplorer;
1141
begin
1142
  codeExplorer := TRunCodeExplorer.Create(true);
1143
  codeExplorer.InputRequestCallback := InputRequestCallback;
1144
  codeExplorer.OutputNotifyCallback := OutputNotifyCallback;
1145
  codeExplorer.PhpExe := GetPHPExe;
1146
  codeExplorer.PhpFile := IncludeTrailingPathDelimiter(ExtractFileDir(Application.ExeName)) + 'codeexplorer.php'; // GetScrapFile;
1147
  codeExplorer.WorkDir := ExtractFileDir(Application.ExeName);
44 daniel-mar 1148
  codeExplorer.Resume;
27 daniel-mar 1149
end;
1150
 
62 daniel-mar 1151
procedure TForm1.StartUpTimerTimer(Sender: TObject);
1152
begin
1153
  StartupTimer.Enabled := false;
1154
 
1155
  // We need this timer because we cannot change the Theme during OnShow,
1156
  // because the Delphi VCL Theme is buggy!
1157
 
1158
  if TFastPHPConfig.DarkTheme then
1159
  begin
1160
    BtnLight.Picture.Assign(BtnLightOff.Picture);
1161
    Theme_Dark;
1162
  end
1163
  else
1164
  begin
1165
    BtnLight.Picture.Assign(BtnLightOn.Picture);
1166
    Theme_Light;
1167
  end;
1168
end;
1169
 
2 daniel-mar 1170
function TForm1.GetScrapFile: string;
49 daniel-mar 1171
var
1172
  tmpPath: string;
2 daniel-mar 1173
begin
56 daniel-mar 1174
  if FSaveAsFilename <> '' then
1175
  begin
1176
    result := FSaveAsFilename;
1177
    exit;
1178
  end;
1179
 
44 daniel-mar 1180
  if FScrapFile <> '' then
1181
  begin
1182
    result := FScrapFile;
1183
    exit;
1184
  end;
19 daniel-mar 1185
 
15 daniel-mar 1186
  if ParamStr(1) <> '' then
49 daniel-mar 1187
  begin
1188
    // Program was started with a filename
1189
 
1190
    result := ParamStr(1);
1191
 
1192
    if not FileExists(result) then
1193
    begin
1194
      case MessageDlg(Format('File %s does not exist. Create it?', [result]), mtConfirmation, mbYesNoCancel, 0) of
1195
        mrYes:
1196
          try
76 daniel-mar 1197
            SaveToFile(result);
49 daniel-mar 1198
          except
1199
            on E: Exception do
1200
            begin
1201
              MessageDlg(E.Message, mtError, [mbOk], 0);
1202
              Application.Terminate;
1203
              result := '';
1204
              exit;
1205
            end;
1206
          end;
1207
        mrNo:
1208
          begin
1209
            Application.Terminate;
1210
            result := '';
1211
            exit;
1212
          end;
1213
        mrCancel:
1214
          begin
1215
            Application.Terminate;
1216
            result := '';
1217
            exit;
1218
          end;
1219
      end;
1220
    end;
1221
  end
13 daniel-mar 1222
  else
2 daniel-mar 1223
  begin
49 daniel-mar 1224
    // Program is started without filename -> use scrap file
2 daniel-mar 1225
 
49 daniel-mar 1226
    result := TFastPHPConfig.ScrapFile;
1227
 
1228
    if not FileExists(result) then
1229
    begin
1230
      repeat
1231
        {$REGION 'Determinate opendialog initial directory'}
1232
        if result <> '' then
1233
        begin
1234
          tmpPath := ExtractFilePath(result);
1235
          if DirectoryExists(tmpPath) then
1236
          begin
1237
            OpenDialog3.InitialDir := tmpPath;
1238
            OpenDialog3.FileName := Result;
1239
          end
1240
          else
1241
          begin
1242
            OpenDialog3.InitialDir := GetMyDocumentsFolder;
1243
          end;
1244
        end
1245
        else
1246
        begin
1247
          OpenDialog3.InitialDir := GetMyDocumentsFolder;
1248
        end;
1249
        {$ENDREGION}
1250
 
1251
        if not OpenDialog3.Execute then
1252
        begin
1253
          Application.Terminate;
1254
          result := '';
1255
          exit;
1256
        end;
1257
 
1258
        if not DirectoryExists(ExtractFilePath(OpenDialog3.FileName)) then
1259
        begin
1260
          MessageDlg('Path does not exist! Please try again.', mtWarning, [mbOk], 0);
1261
        end
1262
        else
1263
        begin
1264
          result := OpenDialog3.FileName;
1265
        end;
1266
      until result <> '';
1267
 
1268
      if not FileExists(result) then
19 daniel-mar 1269
      begin
49 daniel-mar 1270
        try
1271
          // Try saving the file; check if we have permissions
1272
          //SynEdit1.Lines.Clear;
76 daniel-mar 1273
          SaveToFile(result);
49 daniel-mar 1274
        except
1275
          on E: Exception do
1276
          begin
1277
            MessageDlg(E.Message, mtError, [mbOk], 0);
1278
            Application.Terminate;
1279
            result := '';
1280
            exit;
1281
          end;
1282
        end;
19 daniel-mar 1283
      end;
2 daniel-mar 1284
 
49 daniel-mar 1285
      TFastPHPConfig.ScrapFile := result;
1286
      FScrapFile := result;
1287
    end;
2 daniel-mar 1288
  end;
1289
end;
1290
 
1291
procedure TForm1.Help;
1292
var
19 daniel-mar 1293
  IndexFile, chmFile, w, OriginalWord, url: string;
2 daniel-mar 1294
  internalHtmlFile: string;
1295
begin
1296
  if not Assigned(ChmIndex) then
1297
  begin
49 daniel-mar 1298
    IndexFile := TFastPHPConfig.HelpIndex;
2 daniel-mar 1299
    IndexFile := ChangeFileExt(IndexFile, '.ini'); // Just to be sure. Maybe someone wrote manually the ".chm" file in there
1300
    if FileExists(IndexFile) then
1301
    begin
1302
      ChmIndex := TMemIniFile.Create(IndexFile);
1303
    end;
1304
  end;
1305
 
1306
  if Assigned(ChmIndex) then
1307
  begin
49 daniel-mar 1308
    IndexFile := TFastPHPConfig.HelpIndex;
2 daniel-mar 1309
    // We don't check if IndexFile still exists. It is not important since we have ChmIndex pre-loaded in memory
1310
 
1311
    chmFile := ChangeFileExt(IndexFile, '.chm');
1312
    if not FileExists(chmFile) then
1313
    begin
1314
      FreeAndNil(ChmIndex);
1315
    end;
1316
  end;
1317
 
1318
  if not Assigned(ChmIndex) then
1319
  begin
1320
    if not OpenDialog1.Execute then exit;
1321
 
1322
    chmFile := OpenDialog1.FileName;
1323
    if not FileExists(chmFile) then exit;
1324
 
1325
    IndexFile := ChangeFileExt(chmFile, '.ini');
1326
 
1327
    if not FileExists(IndexFile) then
1328
    begin
1329
      Panel1.Align := alClient;
1330
      Panel1.Visible := true;
1331
      Panel1.BringToFront;
1332
      Screen.Cursor := crHourGlass;
1333
      Application.ProcessMessages;
1334
      try
1335
        if not ParseCHM(chmFile) then
1336
        begin
49 daniel-mar 1337
          MessageDlg('The CHM file is not a valid PHP documentation. Cannot use help.', mtError, [mbOk], 0);
2 daniel-mar 1338
          exit;
1339
        end;
1340
      finally
1341
        Screen.Cursor := crDefault;
1342
        Panel1.Visible := false;
1343
      end;
1344
 
1345
      if not FileExists(IndexFile) then
1346
      begin
49 daniel-mar 1347
        MessageDlg('Unknown error. Cannot use help.', mtError, [mbOk], 0);
2 daniel-mar 1348
        exit;
1349
      end;
1350
    end;
1351
 
49 daniel-mar 1352
    TFastPHPConfig.HelpIndex := IndexFile;
2 daniel-mar 1353
 
1354
    ChmIndex := TMemIniFile.Create(IndexFile);
1355
  end;
1356
 
4 daniel-mar 1357
  w := GetWordUnderCaret(SynEdit1);
2 daniel-mar 1358
  if w = '' then exit;
44 daniel-mar 1359
  {$IFDEF UNICODE}
8 daniel-mar 1360
  if CharInSet(w[1], ['0'..'9']) then exit;
44 daniel-mar 1361
  {$ELSE}
1362
  if w[1] in ['0'..'9'] then exit;
1363
  {$ENDIF}
19 daniel-mar 1364
 
1365
  Originalword := w;
1366
//  w := StringReplace(w, '_', '-', [rfReplaceAll]);
2 daniel-mar 1367
  w := LowerCase(w);
1368
  CurSearchTerm := w;
1369
 
72 daniel-mar 1370
  internalHtmlFile := ChmIndex.ReadString('function', CurSearchTerm, '');
2 daniel-mar 1371
  if internalHtmlFile = '' then
72 daniel-mar 1372
    internalHtmlFile := ChmIndex.ReadString('_HelpWords_', CurSearchTerm, '');
1373
  if internalHtmlFile = '' then
2 daniel-mar 1374
  begin
1375
    HelpTabsheet.TabVisible := false;
1376
    HlpPrevPageIndex := -1;
19 daniel-mar 1377
    ShowMessageFmt('No help for "%s" available', [Originalword]);
2 daniel-mar 1378
    Exit;
1379
  end;
1380
 
1381
  url := 'mk:@MSITStore:'+ChmFile+'::'+internalHtmlFile;
1382
 
1383
  HlpPrevPageIndex := PageControl2.ActivePageIndex; // Return by pressing ESC
1384
  HelpTabsheet.TabVisible := true;
1385
  PageControl2.ActivePage := HelpTabsheet;
8 daniel-mar 1386
  WebBrowser2.Navigate(url);
1387
  WebBrowser2.Wait;
2 daniel-mar 1388
end;
1389
 
5 daniel-mar 1390
procedure TForm1.GotoLineNo(LineNo:integer);
1391
var
1392
  line: string;
1393
  i: integer;
2 daniel-mar 1394
begin
5 daniel-mar 1395
  SynEdit1.GotoLineAndCenter(LineNo);
1396
 
1397
  // Skip indent
1398
  line := SynEdit1.Lines[SynEdit1.CaretY];
1399
  for i := 1 to Length(line) do
1400
  begin
44 daniel-mar 1401
    {$IFDEF UNICODE}
8 daniel-mar 1402
    if not CharInSet(line[i], [' ', #9]) then
44 daniel-mar 1403
    {$ELSE}
1404
    if not (line[i] in [' ', #9]) then
1405
    {$ENDIF}
5 daniel-mar 1406
    begin
1407
      SynEdit1.CaretX := i-1;
1408
      break;
1409
    end;
1410
  end;
1411
 
20 daniel-mar 1412
  PageControl2.ActivePage := CodeTabsheet;
5 daniel-mar 1413
  if SynEdit1.CanFocus then SynEdit1.SetFocus;
2 daniel-mar 1414
end;
1415
 
8 daniel-mar 1416
procedure TForm1.PageControl2Changing(Sender: TObject;
1417
  var AllowChange: Boolean);
1418
begin
1419
  if PageControl2.ActivePage = HelpTabsheet then
1420
    HlpPrevPageIndex := -1
1421
  else
1422
    HlpPrevPageIndex := PageControl2.ActivePageIndex;
1423
 
1424
  AllowChange := true;
1425
end;
1426
 
5 daniel-mar 1427
procedure TForm1.Memo2DblClick(Sender: TObject);
1428
var
22 daniel-mar 1429
  line: string;
1430
 
1431
  procedure _process(toFind: string);
1432
  var
1433
    p, lineno: integer;
1434
  begin
1435
    if FileSystemCaseSensitive then
1436
      p := Pos(toFind, line)
1437
    else
44 daniel-mar 1438
      p := Pos(LowerCase(toFind), LowerCase(line));
22 daniel-mar 1439
    if p <> 0 then
1440
    begin
1441
      line := copy(line, p+length(toFind), 99);
1442
      if not TryStrToInt(line, lineno) then exit;
1443
      GotoLineNo(lineno);
1444
    end;
1445
  end;
1446
 
5 daniel-mar 1447
begin
1448
  line := memo2.Lines.Strings[Memo2.CaretPos.Y];
16 daniel-mar 1449
 
18 daniel-mar 1450
  {$REGION 'Possibility 1: filename.php:lineno'}
22 daniel-mar 1451
  _process(ExtractFileName(GetScrapFile) + ':');
18 daniel-mar 1452
  {$ENDREGION}
16 daniel-mar 1453
 
18 daniel-mar 1454
  {$REGION 'Possibility 2: on line xx'}
22 daniel-mar 1455
  _process(ExtractFileName(GetScrapFile) + ' on line ');
18 daniel-mar 1456
  {$ENDREGION}
5 daniel-mar 1457
end;
1458
 
17 daniel-mar 1459
procedure TForm1.Memo2KeyDown(Sender: TObject; var Key: Word;
1460
  Shift: TShiftState);
1461
begin
1462
  if ((ssCtrl in Shift) and (Key = 65)) then TMemo(Sender).SelectAll;
1463
end;
1464
 
5 daniel-mar 1465
function TForm1.MarkUpLineReference(cont: string): string;
18 daniel-mar 1466
 
1467
  procedure _process(toFind: string);
22 daniel-mar 1468
  var
1469
    p, a, b: integer;
1470
    num: integer;
1471
    insert_a, insert_b: string;
5 daniel-mar 1472
  begin
22 daniel-mar 1473
    if FileSystemCaseSensitive then
1474
      p := Pos(toFind, cont)
1475
    else
44 daniel-mar 1476
      p := Pos(LowerCase(toFind), LowerCase(cont));
18 daniel-mar 1477
    while p >= 1 do
5 daniel-mar 1478
    begin
22 daniel-mar 1479
      a := p;
1480
      b := p + length(toFind);
18 daniel-mar 1481
      num := 0;
44 daniel-mar 1482
      {$IFDEF UNICODE}
18 daniel-mar 1483
      while CharInSet(cont[b], ['0'..'9']) do
44 daniel-mar 1484
      {$ELSE}
1485
      while cont[b] in ['0'..'9'] do
1486
      {$ENDIF}
18 daniel-mar 1487
      begin
1488
        num := num*10 + StrToInt(cont[b]);
1489
        inc(b);
1490
      end;
5 daniel-mar 1491
 
18 daniel-mar 1492
      insert_b := '</a>';
22 daniel-mar 1493
      insert_a := '<a href="' + FASTPHP_GOTO_URI_PREFIX + IntToStr(num) + '">';
5 daniel-mar 1494
 
18 daniel-mar 1495
      insert(insert_b, cont, b);
1496
      insert(insert_a, cont, a);
5 daniel-mar 1497
 
18 daniel-mar 1498
      p := b + Length(insert_a) + Length(insert_b);
5 daniel-mar 1499
 
18 daniel-mar 1500
      p := PosEx(toFind, cont, p+1);
1501
    end;
5 daniel-mar 1502
  end;
22 daniel-mar 1503
 
18 daniel-mar 1504
begin
1505
  {$REGION 'Possibility 1: filename.php:lineno'}
22 daniel-mar 1506
  _process(ExtractFileName(GetScrapFile) + ':');
18 daniel-mar 1507
  {$ENDREGION}
5 daniel-mar 1508
 
18 daniel-mar 1509
  {$REGION 'Possibility 2: on line xx'}
22 daniel-mar 1510
  _process(ExtractFileName(GetScrapFile) + ' on line ');
18 daniel-mar 1511
  {$ENDREGION}
1512
 
5 daniel-mar 1513
  result := cont;
1514
end;
1515
 
40 daniel-mar 1516
function TForm1.InputRequestCallback(var data: AnsiString): boolean;
27 daniel-mar 1517
begin
40 daniel-mar 1518
  data := UTF8Encode(SynEdit1.Text);
1519
  result := true;
27 daniel-mar 1520
end;
1521
 
62 daniel-mar 1522
function TForm1.IsThemeDark: boolean;
1523
begin
1524
  result := Assigned(TStyleManager.ActiveStyle) and (TStyleManager.ActiveStyle.Name<>'Windows');
1525
end;
1526
 
40 daniel-mar 1527
function TForm1.OutputNotifyCallback(const data: AnsiString): boolean;
27 daniel-mar 1528
begin
40 daniel-mar 1529
  result := TreeView1.FillWithFastPHPData(data);
27 daniel-mar 1530
end;
1531
 
2 daniel-mar 1532
end.