Subversion Repositories fastphp

Rev

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