Subversion Repositories fastphp

Rev

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