unit CompForm;

{
  Inno Setup
  Copyright (C) 1997-2010 Jordan Russell
  Portions by Martijn Laan
  For conditions of distribution and use, see LICENSE.TXT.

  Compiler form

  $jrsoftware: issrc/Projects/CompForm.pas,v 1.203 2010/04/24 03:28:18 jr Exp $
}

{x$DEFINE STATICCOMPILER}
{ For debugging purposes, remove the 'x' to have it link the compiler code
  into this program and not depend on ISCmplr.dll. }

{$I VERSION.INC}

{$IFDEF IS_D6}
{$WARN SYMBOL_PLATFORM OFF}
{$ENDIF}

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  UIStateForm, StdCtrls, ExtCtrls, Menus, Buttons, ComCtrls, CommCtrl,
  SynEdit, SynEditTypes, SynMemo, SynEditHighlighter,
  {$IFDEF UNICODE} SynHighlighterInnoUniSynEdit, {$ELSE} SynHighlighterInno, {$ENDIF}
  SynHighlighterISXCode, SynHighlighterPas, SynHighlighterMulti, NewTabSet,
  DebugStruct, CompInt, UxThemeISX, dwTaskbarList;

const
  WM_StartCommandLineCompile = WM_USER + $1000;
  WM_StartCommandLineWizard = WM_USER + $1001;
  WM_ShowStartupForm = WM_USER + $1002;

  MRUListMaxCount = 10;

type
  TLineState = (lnUnknown, lnHasEntry, lnEntryProcessed);
  PLineStateArray = ^TLineStateArray;
  TLineStateArray = array[1..1] of TLineState;
  PDebugEntryArray = ^TDebugEntryArray;
  TDebugEntryArray = array[0..0] of TDebugEntry;
  PVariableDebugEntryArray = ^TVariableDebugEntryArray;
  TVariableDebugEntryArray = array[0..0] of TVariableDebugEntry;
  TStepMode = (smRun, smStepInto, smStepOver, smRunToCursor);
  TDebugTarget = (dtSetup, dtUninstall);

const
  DebugTargetStrings: array[TDebugTarget] of String = ('Setup', 'Uninstall');

type
  TCompileForm = class(TUIStateForm)
    MainMenu1: TMainMenu;
    FMenu: TMenuItem;
    FNew: TMenuItem;
    FOpen: TMenuItem;
    FSave: TMenuItem;
    FSaveAs: TMenuItem;
    N1: TMenuItem;
    BCompile: TMenuItem;
    N2: TMenuItem;
    FExit: TMenuItem;
    EMenu: TMenuItem;
    EUndo: TMenuItem;
    N3: TMenuItem;
    ECut: TMenuItem;
    ECopy: TMenuItem;
    EPaste: TMenuItem;
    EDelete: TMenuItem;
    N4: TMenuItem;
    ESelectAll: TMenuItem;
    VMenu: TMenuItem;
    EFind: TMenuItem;
    EFindNext: TMenuItem;
    EReplace: TMenuItem;
    HMenu: TMenuItem;
    HDoc: TMenuItem;
    N6: TMenuItem;
    HAbout: TMenuItem;
    FMRUSep: TMenuItem;
    VCompilerOutput: TMenuItem;
    FindDialog: TFindDialog;
    ReplaceDialog: TReplaceDialog;
    StatusPanel: TPanel;
    CompilerOutputList: TListBox;
    SplitPanel: TPanel;
    ToolbarPanel: TPanel;
    NewButton: TSpeedButton;
    Bevel1: TBevel;
    OpenButton: TSpeedButton;
    SaveButton: TSpeedButton;
    CompileButton: TSpeedButton;
    HelpButton: TSpeedButton;
    HWebsite: TMenuItem;
    VToolbar: TMenuItem;
    N7: TMenuItem;
    TOptions: TMenuItem;
    Memo: TSynEdit;
    SynInnoSyn1: TSynInnoSyn;
    PopupMenu1: TPopupMenu;
    PUndo: TMenuItem;
    N8: TMenuItem;
    PCut: TMenuItem;
    PCopy: TMenuItem;
    PPaste: TMenuItem;
    PDelete: TMenuItem;
    PSelectAll: TMenuItem;
    N9: TMenuItem;
    PFind: TMenuItem;
    PFindNext: TMenuItem;
    PReplace: TMenuItem;
    HFaq: TMenuItem;
    Bevel2: TBevel;
    Bevel3: TBevel;
    StatusBar: TStatusBar;
    BodyPanel: TPanel;
    VStatusBar: TMenuItem;
    ERedo: TMenuItem;
    PRedo: TMenuItem;
    RMenu: TMenuItem;
    RStepInto: TMenuItem;
    RStepOver: TMenuItem;
    N5: TMenuItem;
    RRun: TMenuItem;
    RRunToCursor: TMenuItem;
    N10: TMenuItem;
    REvaluate: TMenuItem;
    CheckIfRunningTimer: TTimer;
    RunButton: TSpeedButton;
    Bevel4: TBevel;
    PauseButton: TSpeedButton;
    RPause: TMenuItem;
    RParameters: TMenuItem;
    ListPopupMenu: TPopupMenu;
    PListCopy: TMenuItem;
    SynMultiSyn1: TSynMultiSyn;
    SynISXCodeSyn1: TSynISXCodeSyn;
    HISPPSep: TMenuItem;
    N12: TMenuItem;
    BStopCompile: TMenuItem;
    StopCompileButton: TSpeedButton;
    HISPPDoc: TMenuItem;
    HISPPWebsite: TMenuItem;
    N13: TMenuItem;
    EGoto: TMenuItem;
    RTerminate: TMenuItem;
    BMenu: TMenuItem;
    BLowPriority: TMenuItem;
    HDonate: TMenuItem;
    N14: TMenuItem;
    HPSWebsite: TMenuItem;
    N15: TMenuItem;
    RTargetSetup: TMenuItem;
    RTargetUninstall: TMenuItem;
    TargetSetupButton: TSpeedButton;
    TargetUninstallButton: TSpeedButton;
    Bevel5: TBevel;
    TabSet: TNewTabSet;
    DebugOutputList: TListBox;
    VDebugOutput: TMenuItem;
    VHide: TMenuItem;
    N11: TMenuItem;
    SpacerPaintBox: TPaintBox;
    TMenu: TMenuItem;
    TAddRemovePrograms: TMenuItem;
    RToggleBreakPoint: TMenuItem;
    HWhatsNew: TMenuItem;
    TGenerateGUID: TMenuItem;
    TSignTools: TMenuItem;
    N16: TMenuItem;
    HExamples: TMenuItem;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure FExitClick(Sender: TObject);
    procedure FOpenClick(Sender: TObject);
    procedure EUndoClick(Sender: TObject);
    procedure EMenuClick(Sender: TObject);
    procedure PopupMenu1Popup(Sender: TObject);
    procedure ECutClick(Sender: TObject);
    procedure ECopyClick(Sender: TObject);
    procedure EPasteClick(Sender: TObject);
    procedure EDeleteClick(Sender: TObject);
    procedure FSaveClick(Sender: TObject);
    procedure ESelectAllClick(Sender: TObject);
    procedure FNewClick(Sender: TObject);
    procedure FNewWizardClick(Sender: TObject);
    procedure FSaveAsClick(Sender: TObject);
    procedure HDocClick(Sender: TObject);
    procedure BCompileClick(Sender: TObject);
    procedure FMenuClick(Sender: TObject);
    procedure FMRUClick(Sender: TObject);
    procedure VCompilerOutputClick(Sender: TObject);
    procedure HAboutClick(Sender: TObject);
    procedure EFindClick(Sender: TObject);
    procedure FindDialogFind(Sender: TObject);
    procedure EReplaceClick(Sender: TObject);
    procedure ReplaceDialogReplace(Sender: TObject);
    procedure EFindNextClick(Sender: TObject);
    procedure SplitPanelMouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
    procedure VMenuClick(Sender: TObject);
    procedure HWebsiteClick(Sender: TObject);
    procedure VToolbarClick(Sender: TObject);
    procedure TOptionsClick(Sender: TObject);
    procedure HFaqClick(Sender: TObject);
    procedure HPSWebsiteClick(Sender: TObject);
    procedure HISPPDocClick(Sender: TObject);
    procedure HISPPWebsiteClick(Sender: TObject);
    procedure MemoStatusChange(Sender: TObject;
      Changes: TSynStatusChanges);
    procedure VStatusBarClick(Sender: TObject);
    procedure MemoSpecialLineColors(Sender: TObject; Line: Integer;
      var Special: Boolean; var FG, BG: TColor);
    procedure MemoChange(Sender: TObject);
    procedure MemoDropFiles(Sender: TObject; X, Y: Integer;
      AFiles: TStrings);
    procedure ERedoClick(Sender: TObject);
    procedure StatusBarResize(Sender: TObject);
    procedure MemoPaint(Sender: TObject; ACanvas: TCanvas);
    procedure RStepIntoClick(Sender: TObject);
    procedure RStepOverClick(Sender: TObject);
    procedure RRunToCursorClick(Sender: TObject);
    procedure RRunClick(Sender: TObject);
    procedure REvaluateClick(Sender: TObject);
    procedure CheckIfRunningTimerTimer(Sender: TObject);
    procedure RPauseClick(Sender: TObject);
    procedure RParametersClick(Sender: TObject);
    procedure PListCopyClick(Sender: TObject);
    procedure BStopCompileClick(Sender: TObject);
    procedure HMenuClick(Sender: TObject);
    procedure EGotoClick(Sender: TObject);
    procedure MemoKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure RTerminateClick(Sender: TObject);
    procedure BMenuClick(Sender: TObject);
    procedure BLowPriorityClick(Sender: TObject);
    procedure StatusBarDrawPanel(StatusBar: TStatusBar;
      Panel: TStatusPanel; const Rect: TRect);
    procedure HDonateClick(Sender: TObject);
    procedure RTargetClick(Sender: TObject);
    procedure DebugOutputListDrawItem(Control: TWinControl; Index: Integer;
      Rect: TRect; State: TOwnerDrawState);
    procedure TabSetClick(Sender: TObject);
    procedure VHideClick(Sender: TObject);
    procedure VDebugOutputClick(Sender: TObject);
    procedure FormResize(Sender: TObject);
    procedure TAddRemoveProgramsClick(Sender: TObject);
{$IFDEF UNICODE}
    procedure MemoGutterClick(Sender: TObject; Button: TMouseButton;X, Y, Line: Integer;
      mark: TSynEditMark);
{$ELSE}
    procedure MemoGutterClick(Sender: TObject; X, Y, Line: Integer;
      mark: TSynEditMark);
{$ENDIF}
    procedure RToggleBreakPointClick(Sender: TObject);
    procedure HWhatsNewClick(Sender: TObject);
    procedure TGenerateGUIDClick(Sender: TObject);
    procedure TSignToolsClick(Sender: TObject);
    procedure HExamplesClick(Sender: TObject);
  private
    { Private declarations }
    FCompilerVersion: PCompilerVersionInfo;
    FFilename: String;
    FFileLastWriteTime: TFileTime;
    FMRUMenuItems: array[0..MRUListMaxCount-1] of TMenuItem;
    FMRUList: TStringList;
    FOptions: record
      ShowStartupForm: Boolean;
      UseWizard: Boolean;
      Autosave: Boolean;
      MakeBackups: Boolean;
      FullPathInTitleBar: Boolean;
      UndoAfterSave: Boolean;
      PauseOnDebuggerExceptions: Boolean;
      RunAsDifferentUser: Boolean;
      UseSyntaxHighlighting: Boolean;
      CursorPastEOL: Boolean;
      TabWidth: Integer;
      LowPriorityDuringCompile: Boolean;
    end;
    FSignTools: TStringList;
    FCompiling: Boolean;
    FCompileWantAbort: Boolean;
    FBecameIdle: Boolean;
    FErrorLine, FStepLine: Integer;
    FModifiedSinceLastCompile, FModifiedSinceLastCompileAndGo: Boolean;
    FDebugEntries: PDebugEntryArray;
    FDebugEntriesCount: Integer;
    FVariableDebugEntries: PVariableDebugEntryArray;
    FVariableDebugEntriesCount: Integer;
    FCompiledCodeText: AnsiString;
    FCompiledCodeDebugInfo: AnsiString;
    FLineState: PLineStateArray;
    FLineStateCapacity, FLineStateCount: Integer;
    FDebugClientWnd: HWND;
    FProcessHandle, FDebugClientProcessHandle: THandle;
    FDebugTarget: TDebugTarget;
    FCompiledExe, FUninstExe, FTempDir: String;
    FDebugging: Boolean;
    FStepMode: TStepMode;
    FPaused: Boolean;
    FRunToCursorPoint: TDebugEntry;
    FReplyString: String;
    FDebuggerException: String;
    FRunParameters: String;
    FLastFindOptions: TFindOptions;
    FLastFindText: String;
    FLastReplaceText: String;
    FLastEvaluateConstantText: String;
    FSavePriorityClass: DWORD;
    FBuildImageList: HIMAGELIST;
    FBuildAnimationFrame: Cardinal;
    FLastAnimationTick: DWORD;
    FProgress, FProgressMax: Cardinal;
    FProgressThemeData: HTHEME;
    FProgressChunkSize, FProgressSpaceSize: Integer;
    FDebugLogListTimeWidth: Integer;
    FGutterImageList: HIMAGELIST;
    FBreakPoints: TList;
    FTaskbarList3: ITaskbarList3;
    class procedure AppOnException(Sender: TObject; E: Exception);
    procedure AppOnActivate(Sender: TObject);
    procedure AppOnIdle(Sender: TObject; var Done: Boolean);
    function AskToDetachDebugger: Boolean;
    procedure BringToForeground;
    procedure CheckIfTerminated;
    procedure CompileFile(AFilename: String; const ReadFromFile: Boolean);
    procedure CompileIfNecessary;
    function ConfirmCloseFile(const PromptToSave: Boolean): Boolean;
    procedure DebuggingStopped(const WaitForTermination: Boolean);
    procedure DebugLogMessage(const S: String);
    procedure DestroyDebugInfo;
    procedure DetachDebugger;
    function EvaluateConstant(const S: String; var Output: String): Integer;
    function EvaluateVariableEntry(const DebugEntry: PVariableDebugEntry;
      var Output: String): Integer;
    procedure FindNext;
    procedure Go(AStepMode: TStepMode);
    procedure HideError;
    procedure InitializeFindText(Dlg: TFindDialog);
    procedure InvalidateStatusPanel(const Index: Integer);
    procedure MemoLinesDeleted(FirstLine, Count: integer);
    procedure MemoLinesInserted(FirstLine, Count: integer);
    procedure MemoWndProc(var Message: TMessage);
    procedure ModifyMRUList(const AFilename: String; const AddNewItem: Boolean);
    procedure MoveCaret(const LineNumber: Integer; const AlwaysResetColumn: Boolean);
    procedure NewFile;
    procedure NewWizardFile;
    procedure OpenFile(AFilename: String; const AddToRecentDocs: Boolean);
    procedure OpenMRUFile(const AFilename: String);
    procedure ParseDebugInfo(DebugInfo: Pointer);
    procedure ReadMRUList;
    procedure ResetLineState;
    procedure StartProcess;
    function SaveFile(const SaveAs: Boolean): Boolean;
    procedure SetErrorLine(ALine: Integer);
    procedure SetLowPriority(ALowPriority: Boolean);
    procedure SetStatusPanelVisible(const AVisible: Boolean);
    procedure SetStepLine(ALine: Integer);
    procedure ShowOpenDialog(const Examples: Boolean);
    procedure StatusMessage(const S: String);
    procedure SyncEditorOptions;
    procedure ToggleBreakPoint(Line: Integer);
    procedure UpdateCaption;
    procedure UpdateCompileStatusPanels(const AProgress, AProgressMax: Cardinal;
      const ASecondsRemaining: Integer; const ABytesCompressedPerSecond: Cardinal);
    procedure UpdateNewButtons;
    procedure UpdateRunMenu;
    procedure UpdateTargetMenu;
    procedure UpdateThemeData(const Close, Open: Boolean);
    procedure UpdateStatusPanelHeight(H: Integer);
    procedure WMCopyData(var Message: TWMCopyData); message WM_COPYDATA;
    procedure WMDebuggerHello(var Message: TMessage); message WM_Debugger_Hello;
    procedure WMDebuggerGoodbye(var Message: TMessage); message WM_Debugger_Goodbye;
    procedure WMDebuggerQueryVersion(var Message: TMessage); message WM_Debugger_QueryVersion;
    function GetLineNumberFromEntry(Kind, Index: Integer): Integer;
    procedure DebuggerStepped(var Message: TMessage; const Intermediate: Boolean);
    procedure WMDebuggerStepped(var Message: TMessage); message WM_Debugger_Stepped;
    procedure WMDebuggerSteppedIntermediate(var Message: TMessage); message WM_Debugger_SteppedIntermediate;
    procedure WMDebuggerException(var Message: TMessage); message WM_Debugger_Exception;
    procedure WMDebuggerSetForegroundWindow(var Message: TMessage); message WM_Debugger_SetForegroundWindow;
    procedure WMStartCommandLineCompile(var Message: TMessage); message WM_StartCommandLineCompile;
    procedure WMStartCommandLineWizard(var Message: TMessage); message WM_StartCommandLineWizard;
    procedure WMShowStartupForm(var Message: TMessage); message WM_ShowStartupForm;
    procedure WMThemeChanged(var Message: TMessage); message WM_THEMECHANGED;
{$IFDEF IS_D4}
  protected
    procedure WndProc(var Message: TMessage); override;
{$ENDIF}
  public
    { Public declarations }
  end;

var
  CompileForm: TCompileForm;

  CommandLineFilename, CommandLineWizardName: String;
  CommandLineCompile: Boolean;
  CommandLineWizard: Boolean;

function GenerateGuid: String;
procedure InitFormFont(Form: TForm);

implementation

uses
  ActiveX, Clipbrd, ShellApi, ShlObj, IniFiles, Registry, CommDlg,
  PathFunc, CmnFunc, CmnFunc2, FileClass, CompMsgs, TmSchemaISX, BrowseFunc, HtmlHelpFunc,
  {$IFDEF STATICCOMPILER} Compile, {$ENDIF}
  CompOptions, CompStartup, CompWizard, CompSignTools{$IFDEF UNICODE}, SynEditSearch{$ENDIF};

{$R *.DFM}
{$R CompImages.res}

const
  { Status bar panel indexes }
  spCaretPos = 0;
  spModified = 1;
  spInsertMode = 2;
  spCompileIcon = 3;
  spCompileProgress = 4;
  spExtraStatus = 5;

  { Tab set indexes }
  tiCompilerOutput = 0;
  tiDebugOutput = 1;

  LineStateGrowAmount = 4000;

procedure InitFormFont(Form: TForm);
var
  FontName: String;
  Metrics: TNonClientMetrics;
begin
{$IFNDEF UNICODE}
  if Win32MajorVersion < 5 then begin
    { On pre-2000 Windows, just use MS Sans Serif always, except on Japanese }
    if DefFontData.Charset = SHIFTJIS_CHARSET then begin
      { MS Sans Serif can't display Japanese characters, so revert to the
        default Japanese font (requires D3+) }
      Form.Font.Handle := 0;
      Exit;
    end;
    FontName := GetPreferredUIFont;
  end
  else
{$ENDIF}
  begin
    Metrics.cbSize := SizeOf(Metrics);  { <- won't work on Delphi 2010? }
    if SystemParametersInfo(SPI_GETNONCLIENTMETRICS, SizeOf(Metrics),
       @Metrics, 0) then
      FontName := Metrics.lfMessageFont.lfFaceName;
    { Only allow fonts that we know will fit the text correctly }
    if CompareText(FontName, 'Microsoft Sans Serif') <> 0 then
      FontName := 'Tahoma';
  end;
  Form.Font.Name := FontName;
  Form.Font.Size := 8;
end;

function GetDisplayFilename(const Filename: String): String;
var
  Buf: array[0..MAX_PATH-1] of Char;
begin
  if GetFileTitle(PChar(Filename), Buf, SizeOf(Buf)) = 0 then
    Result := Buf
  else
    Result := Filename;
end;

function GetLastWriteTimeOfFile(const Filename: String;
  var LastWriteTime: TFileTime): Boolean;
var
  H: THandle;
begin
  H := CreateFile(PChar(Filename), 0, FILE_SHARE_READ or FILE_SHARE_WRITE,
    nil, OPEN_EXISTING, 0, 0);
  if H <> INVALID_HANDLE_VALUE then begin
    Result := GetFileTime(H, nil, nil, @LastWriteTime);
    CloseHandle(H);
  end
  else
    Result := False;
end;

procedure AddFileToRecentDocs(const Filename: String);
{ Notifies the shell that a document has been opened. On Windows 7, this will
  add the file to the Recent section of the app's Jump List.
  It is only necessary to call this function when the shell is unaware that
  a file is being opened. Files opened through Explorer or common dialogs get
  added to the Jump List automatically. }
begin
  SHAddToRecentDocs(
    {$IFDEF UNICODE} SHARD_PATHW {$ELSE} SHARD_PATHA {$ENDIF},
    PChar(Filename));
end;

function GenerateGuid: String;
var
  Guid: TGUID;
  P: PWideChar;
begin
  if CoCreateGuid(Guid) <> S_OK then
    raise Exception.Create('CoCreateGuid failed');
  if StringFromCLSID(Guid, P) <> S_OK then
    raise Exception.Create('StringFromCLSID failed');
  try
    Result := P;
  finally
    CoTaskMemFree(P);
  end;
end;

{ TConfigIniFile }

type
  TConfigIniFile = class(TRegIniFile)
  private
    FMutex: THandle;
    FAcquiredMutex: Boolean;
  public
    constructor Create;
    destructor Destroy; override;
  end;

constructor TConfigIniFile.Create;
begin
  inherited Create('Software\Jordan Russell\Inno Setup');
  { Paranoia: Use a mutex to prevent multiple instances from reading/writing
    to the registry simultaneously }
  FMutex := CreateMutex(nil, False, 'Inno-Setup-IDE-Config-Mutex');
  if FMutex <> 0 then
    if WaitForSingleObject(FMutex, INFINITE) <> WAIT_FAILED then
      FAcquiredMutex := True;
end;

destructor TConfigIniFile.Destroy;
begin
  if FMutex <> 0 then begin
    if FAcquiredMutex then
      ReleaseMutex(FMutex);
    CloseHandle(FMutex);
  end;
  inherited;
end;

{ TCompileFormMemoPlugin }

type
  TCompileFormMemoPlugin = class(TSynEditPlugin)
  private
    FForm: TCompileForm;
  protected
{$IFDEF UNICODE}
    procedure AfterPaint(ACanvas: TCanvas; const AClip: TRect;
       FirstLine, LastLine: Integer); override;
{$ELSE}
    procedure AfterPaint(ACanvas: TCanvas; AClip: TRect;
      FirstLine, LastLine: integer); override;
{$ENDIF}
    procedure LinesInserted(FirstLine, Count: integer); override;
    procedure LinesDeleted(FirstLine, Count: integer); override;
  public
    constructor Create(AForm: TCompileForm);
  end;

constructor TCompileFormMemoPlugin.Create(AForm: TCompileForm);
begin
  inherited Create(AForm.Memo);
  FForm := AForm;
end;

{$IFDEF UNICODE}
procedure TCompileFormMemoPlugin.AfterPaint(ACanvas: TCanvas; const AClip: TRect;
  FirstLine, LastLine: integer);
{$ELSE}
procedure TCompileFormMemoPlugin.AfterPaint(ACanvas: TCanvas; AClip: TRect;
  FirstLine, LastLine: integer);
{$ENDIF}
begin
end;

procedure TCompileFormMemoPlugin.LinesInserted(FirstLine, Count: integer);
begin
  FForm.MemoLinesInserted(FirstLine, Count);
end;

procedure TCompileFormMemoPlugin.LinesDeleted(FirstLine, Count: integer);
begin
  FForm.MemoLinesDeleted(FirstLine, Count);
end;

{ TCompileForm }

procedure TCompileForm.FormCreate(Sender: TObject);

  procedure ReadConfig;
{$IFNDEF UNICODE}
  const
    { "MS Gothic" in Japanese (CP 932) }
    SMSGothicLocalized = #$82'l'#$82'r '#$83'S'#$83'V'#$83'b'#$83'N';
{$ENDIF}
  var
    Ini: TConfigIniFile;
    WindowPlacement: TWindowPlacement;
  begin
    Ini := TConfigIniFile.Create;
    try
      { Menu check boxes state }
      ToolbarPanel.Visible := Ini.ReadBool('Options', 'ShowToolbar', True);
      StatusBar.Visible := Ini.ReadBool('Options', 'ShowStatusBar', True);
      FOptions.LowPriorityDuringCompile := Ini.ReadBool('Options', 'LowPriorityDuringCompile', False);

      { Configuration options }
      FOptions.ShowStartupForm := Ini.ReadBool('Options', 'ShowStartupForm', True);
      FOptions.UseWizard := Ini.ReadBool('Options', 'UseWizard', True);
      FOptions.Autosave := Ini.ReadBool('Options', 'Autosave', False);
      FOptions.MakeBackups := Ini.ReadBool('Options', 'MakeBackups', False);
      FOptions.FullPathInTitleBar := Ini.ReadBool('Options', 'FullPathInTitleBar', False);
      FOptions.UndoAfterSave := Ini.ReadBool('Options', 'UndoAfterSave', False);
      FOptions.PauseOnDebuggerExceptions := Ini.ReadBool('Options', 'PauseOnDebuggerExceptions', True);
      FOptions.RunAsDifferentUser := Ini.ReadBool('Options', 'RunAsDifferentUser', False);
      FOptions.UseSyntaxHighlighting := Ini.ReadBool('Options', 'UseSynHigh', True);
      FOptions.CursorPastEOL := Ini.ReadBool('Options', 'EditorCursorPastEOL', True);
      FOptions.TabWidth := Ini.ReadInteger('Options', 'TabWidth', 2);
      SyncEditorOptions;
      Memo.Font.Size := 10;  { Scaled is False, so make sure it's 10pt scaled }
      if GetACP = 932 then begin
        { Default to MS Gothic font on CP 932 (Japanese), as Courier New is
          only capable of displaying Japanese characters on XP and later. }
{$IFNDEF UNICODE}
        { Use the English name if it's supported on this version of Windows
          (I believe it was first added in Windows 2000), because the CP 932
          localized Japanese name will no longer be valid if the user later
          switches out of CP 932. }
        if FontExists('MS Gothic') then
          Memo.Font.Name := 'MS Gothic'
        else
          Memo.Font.Name := SMSGothicLocalized;
{$ELSE}
        { UNICODE requires 2000+, so we can just use the English name }
        Memo.Font.Name := 'MS Gothic';
{$ENDIF}
        Memo.Font.Size := 9;
        Memo.Font.Charset := SHIFTJIS_CHARSET;
      end;
      Memo.Font.Name := Ini.ReadString('Options', 'EditorFontName', Memo.Font.Name);
      Memo.Font.Size := Ini.ReadInteger('Options', 'EditorFontSize', Memo.Font.Size);
      Memo.Font.Charset := Ini.ReadInteger('Options', 'EditorFontCharset', Memo.Font.Charset);
      UpdateNewButtons;

      { Window state }
      WindowPlacement.length := SizeOf(WindowPlacement);
      GetWindowPlacement(Handle, @WindowPlacement);
      WindowPlacement.showCmd := SW_HIDE;  { the form isn't Visible yet }
      WindowPlacement.rcNormalPosition.Left := Ini.ReadInteger('State',
        'WindowLeft', WindowPlacement.rcNormalPosition.Left);
      WindowPlacement.rcNormalPosition.Top := Ini.ReadInteger('State',
        'WindowTop', WindowPlacement.rcNormalPosition.Top);
      WindowPlacement.rcNormalPosition.Right := Ini.ReadInteger('State',
        'WindowRight', WindowPlacement.rcNormalPosition.Left + Width);
      WindowPlacement.rcNormalPosition.Bottom := Ini.ReadInteger('State',
        'WindowBottom', WindowPlacement.rcNormalPosition.Top + Height);
      SetWindowPlacement(Handle, @WindowPlacement);
      { Note: Must set WindowState *after* calling SetWindowPlacement, since
        TCustomForm.WMSize resets WindowState }
      if Ini.ReadBool('State', 'WindowMaximized', False) then
        WindowState := wsMaximized;
      { Note: Don't call UpdateStatusPanelHeight here since it clips to the
        current form height, which hasn't been finalized yet }
      StatusPanel.Height := Ini.ReadInteger('State', 'StatusPanelHeight',
        StatusPanel.Height);
    finally
      Ini.Free;
    end;
  end;

  procedure ReadSignTools;
  var
    Ini: TConfigIniFile;
    I: Integer;
    S: String;
  begin
    Ini := TConfigIniFile.Create;
    try
      { Sign tools }
      FSignTools.Clear();
      I := 0;
      repeat
        S := Ini.ReadString('SignTools', 'SignTool' + IntToStr(I), '');
        if S <> '' then
          FSignTools.Add(S);
        Inc(I);
      until S = '';
    finally
      Ini.Free;
    end;
  end;


var
  I: Integer;
  NewItem: TMenuItem;
begin
  {$IFNDEF STATICCOMPILER}
  FCompilerVersion := ISDllGetVersion;
  {$ELSE}
  FCompilerVersion := ISGetVersion;
  {$ENDIF}

  FModifiedSinceLastCompile := True;

  InitFormFont(Self);

  FBuildImageList := ImageList_LoadBitmap(HInstance, 'BUILDIMAGES', 17, 0, clSilver);
  FGutterImageList := ImageList_LoadBitmap(HInstance, 'GUTTERIMAGES', 9, 0, clFuchsia);

  { For some reason, if AutoScroll=False is set on the form Delphi ignores the
    'poDefault' Position setting }
  AutoScroll := False;

  { Append 'Del' to the end of the Delete item. Don't actually use Del as
    the shortcut key so that the Del key still works when the menu item is
    disabled because there is no selection. }
  EDelete.Caption := EDelete.Caption + #9 + ShortCutToText(VK_DELETE);

  Memo.WindowProc := MemoWndProc;
{$IFDEF UNICODE}
  Memo.SearchEngine := TSynEditSearch.Create(Self);
{$ENDIF}

  FBreakPoints := TList.Create;
  TCompileFormMemoPlugin.Create(Self);

  DebugOutputList.Canvas.Font.Assign(DebugOutputList.Font);
  FDebugLogListTimeWidth := DebugOutputList.Canvas.TextWidth(Format(
    '[00%s00%s00%s000]   ', [TimeSeparator, TimeSeparator, DecimalSeparator]));
  DebugOutputList.ItemHeight := DebugOutputList.Canvas.TextHeight('0');

  Application.OnException := AppOnException;
  Application.OnActivate := AppOnActivate;
  Application.OnIdle := AppOnIdle;

  FMRUList := TStringList.Create;
  for I := 0 to High(FMRUMenuItems) do begin
    NewItem := TMenuItem.Create(Self);
    NewItem.OnClick := FMRUClick;
    FMenu.Insert(FMenu.IndexOf(FMRUSep), NewItem);
    FMRUMenuItems[I] := NewItem;
  end;

  FSignTools := TStringList.Create;

  try
    if CoCreateInstance(CLSID_TaskbarList, nil, CLSCTX_INPROC_SERVER, IID_TaskbarList3, FTaskbarList3) = S_OK then
      FTaskbarList3.HrInit;
  except
    FTaskbarList3 := nil;
  end;

  FDebugTarget := dtSetup;
  UpdateTargetMenu;

  UpdateCaption;

  UpdateThemeData(False, True);

  if CommandLineCompile then begin
    ReadSignTools;
    PostMessage(Handle, WM_StartCommandLineCompile, 0, 0)
  end else if CommandLineWizard then begin
    { Stop Delphi from showing the compiler form }
    Application.ShowMainForm := False;
    { Show wizard form later }
    PostMessage(Handle, WM_StartCommandLineWizard, 0, 0);
  end else begin
    ReadConfig;
    ReadSignTools;
    if CommandLineFilename = '' then begin
      if FOptions.ShowStartupForm then
        PostMessage(Handle, WM_ShowStartupForm, 0, 0);
    end else
      OpenFile(CommandLineFilename, False);
  end;
end;

procedure TCompileForm.FormDestroy(Sender: TObject);

  procedure SaveConfig;
  var
    Ini: TConfigIniFile;
    WindowPlacement: TWindowPlacement;
  begin
    Ini := TConfigIniFile.Create;
    try
      { Menu check boxes state }
      Ini.WriteBool('Options', 'ShowToolbar', ToolbarPanel.Visible);
      Ini.WriteBool('Options', 'ShowStatusBar', StatusBar.Visible);
      Ini.WriteBool('Options', 'LowPriorityDuringCompile', FOptions.LowPriorityDuringCompile);

      { Window state }
      WindowPlacement.length := SizeOf(WindowPlacement);
      GetWindowPlacement(Handle, @WindowPlacement);
      Ini.WriteInteger('State', 'WindowLeft', WindowPlacement.rcNormalPosition.Left);
      Ini.WriteInteger('State', 'WindowTop', WindowPlacement.rcNormalPosition.Top);
      Ini.WriteInteger('State', 'WindowRight', WindowPlacement.rcNormalPosition.Right);
      Ini.WriteInteger('State', 'WindowBottom', WindowPlacement.rcNormalPosition.Bottom);
      Ini.WriteBool('State', 'WindowMaximized', WindowState = wsMaximized);
      Ini.WriteInteger('State', 'StatusPanelHeight', StatusPanel.Height);
    finally
      Ini.Free;
    end;
  end;

begin
  UpdateThemeData(True, False);

  Application.OnActivate := nil;
  Application.OnIdle := nil;

  if not (CommandLineCompile or CommandLineWizard) then
    SaveConfig;

  FBreakPoints.Free;
  DestroyDebugInfo;
  FSignTools.Free;
  FMRUList.Free;
  ImageList_Destroy(FGutterImageList);
  FGutterImageList := 0;
  ImageList_Destroy(FBuildImageList);
  FBuildImageList := 0;
end;

class procedure TCompileForm.AppOnException(Sender: TObject; E: Exception);
begin
  AppMessageBox(PChar(AddPeriod(E.Message)), SCompilerFormCaption,
    MB_OK or MB_ICONSTOP);
end;

procedure TCompileForm.FormCloseQuery(Sender: TObject;
  var CanClose: Boolean);
begin
  if IsWindowEnabled(Application.Handle) then
    CanClose := ConfirmCloseFile(True)
  else
    { CloseQuery is also called by the VCL when a WM_QUERYENDSESSION message
      is received. Don't display message box if a modal dialog is already
      displayed. }
    CanClose := False;
end;

procedure TCompileForm.FormResize(Sender: TObject);
begin
  { Make sure the status panel's height is decreased if necessary in response
    to the form's height decreasing }
  if StatusPanel.Visible then
    UpdateStatusPanelHeight(StatusPanel.Height);
end;

{$IFDEF IS_D4}
procedure TCompileForm.WndProc(var Message: TMessage);
begin
  { Without this, the status bar's owner drawn panels sometimes get corrupted and show
    menu items instead. See:
    http://groups.google.com/group/borland.public.delphi.vcl.components.using/browse_thread/thread/e4cb6c3444c70714 }
  with Message do
    case Msg of
      WM_DRAWITEM:
        with PDrawItemStruct(Message.LParam)^ do
          if (CtlType = ODT_MENU) and not IsMenu(hwndItem) then
            CtlType := ODT_STATIC;
    end;
  inherited 
end;
{$ENDIF}

procedure TCompileForm.UpdateCaption;
var
  NewCaption: String;
begin
  if FFilename = '' then
    NewCaption := 'Untitled'
  else begin
    if FOptions.FullPathInTitleBar then
      NewCaption := FFilename
    else
      NewCaption := GetDisplayFilename(FFilename);
  end;
  NewCaption := NewCaption + ' - ' + SCompilerFormCaption + ' ' +
    String(FCompilerVersion.Version);
  if FCompiling then
    NewCaption := NewCaption + '  [Compiling]'
  else if FDebugging then begin
    if not FPaused then
      NewCaption := NewCaption + '  [Running]'
    else
      NewCaption := NewCaption + '  [Paused]';
  end;
  Caption := NewCaption;
  if not CommandLineWizard then
    Application.Title := NewCaption;
end;

procedure TCompileForm.UpdateNewButtons;
begin
  if FOptions.UseWizard then begin
    FNew.OnClick := FNewWizardClick;
    NewButton.OnClick := FNewWizardClick;
  end else begin
    FNew.OnClick := FNewClick;
    NewButton.OnClick := FNewClick;
  end;
end;

procedure TCompileForm.NewFile;
begin
  FUninstExe := '';
  if FDebugTarget <> dtSetup then begin
    FDebugTarget := dtSetup;
    UpdateTargetMenu;
  end;
  FBreakPoints.Clear;
  DestroyDebugInfo;
  Memo.Text := '';
  FModifiedSinceLastCompile := True;
  Memo.Modified := False;
  FFilename := '';
  UpdateCaption;
  HideError;
end;

procedure TCompileForm.NewWizardFile;
var
  WizardForm: TWizardForm;
  SaveEnabled: Boolean;
  F: TTextFileWriter;
begin
  WizardForm := TWizardForm.Create(Application);
  try
    SaveEnabled := Enabled;
    if CommandLineWizard then begin
      WizardForm.WizardName := CommandLineWizardName;
      { Must disable CompileForm even though it isn't shown, otherwise
        menu keyboard shortcuts (such as Ctrl+O) still work }
      Enabled := False;
    end;
    try
      if WizardForm.ShowModal <> mrOk then
        Exit;
    finally
      Enabled := SaveEnabled;
    end;

    if CommandLineWizard then begin
      F := TTextFileWriter.Create(CommandLineFileName, fdCreateAlways, faWrite, fsNone);
      try
{$IFDEF UNICODE}
        if SameText(WizardForm.ResultScript, String(AnsiString(WizardForm.ResultScript))) then
          F.WriteAnsi(AnsiString(WizardForm.ResultScript))
        else
          F.Write(WizardForm.ResultScript);
{$ELSE}
        F.Write(WizardForm.ResultScript);
{$ENDIF}
      finally
        F.Free;
      end;
    end else begin
      NewFile;
      Memo.Text := WizardForm.ResultScript;
      Memo.Modified := (WizardForm.Result = wrComplete);

      if WizardForm.Result = wrComplete then
        if MsgBox('Would you like to compile the new script now?', SCompilerFormCaption, mbConfirmation, MB_YESNO) = IDYES then
          BCompileClick(Self);
    end;
  finally
    WizardForm.Free;
  end;
end;

procedure TCompileForm.OpenFile(AFilename: String;
  const AddToRecentDocs: Boolean);
var
  Stream: TFileStream;
begin
  AFilename := PathExpand(AFilename);

  FUninstExe := '';
  if FDebugTarget <> dtSetup then begin
    FDebugTarget := dtSetup;
    UpdateTargetMenu;
  end;
  FBreakPoints.Clear;
  DestroyDebugInfo;
  Stream := TFileStream.Create(AFilename, fmOpenRead or fmShareDenyNone);
  try
    GetFileTime(Stream.Handle, nil, nil, @FFileLastWriteTime);
    Memo.Lines.LoadFromStream(Stream);
  finally
    Stream.Free;
  end;
  FModifiedSinceLastCompile := True;
  Memo.Modified := False;
  FFilename := AFilename;
  UpdateCaption;
  HideError;
  ModifyMRUList(AFilename, True);
  if AddToRecentDocs then
    AddFileToRecentDocs(AFilename);
end;

procedure TCompileForm.OpenMRUFile(const AFilename: String);
{ Same as OpenFile, but offers to remove the file from the MRU list if it
  cannot be opened }
begin
  try
    OpenFile(AFilename, True);
  except
    Application.HandleException(Self);
    if MsgBoxFmt('There was an error opening the file. Remove it from the list?',
       [AFilename], SCompilerFormCaption, mbError, MB_YESNO) = IDYES then
      ModifyMRUList(AFilename, False);
  end;
end;

function TCompileForm.SaveFile(const SaveAs: Boolean): Boolean;

  procedure SaveTo(const FN: String);
  var
    TempFN, BackupFN, Script: String;
    Buf: array[0..4095] of Char;
    F: TTextFileWriter;
  begin
    { Save to a temporary file; don't overwrite existing files in place. This
      way, if the system crashes or the disk runs out of space during the save,
      the existing file will still be intact. }
    if GetTempFileName(PChar(PathExtractDir(FN)), 'iss', 0, Buf) = 0 then
      raise Exception.CreateFmt('Error creating file (code %d). Could not save file',
        [GetLastError]);
    TempFN := Buf;
    try
      Script := Memo.Lines.Text;
      F := TTextFileWriter.Create(TempFN, fdCreateAlways, faWrite, fsNone);
      try
{$IFDEF UNICODE}
        if SameText(Script, String(AnsiString(Script))) then
          F.WriteAnsi(AnsiString(Script))
        else
          F.Write(Script);
{$ELSE}
        F.Write(Script);
{$ENDIF}
      finally
        F.Free;
      end;

      { Back up existing file if needed }
      if FOptions.MakeBackups and NewFileExists(FN) then begin
        BackupFN := PathChangeExt(FN, '.~is');
        DeleteFile(BackupFN);
        if not RenameFile(FN, BackupFN) then
          raise Exception.Create('Error creating backup file. Could not save file');
      end;

      { Delete existing file }
      if not DeleteFile(FN) and (GetLastError <> ERROR_FILE_NOT_FOUND) then
        raise Exception.CreateFmt('Error removing existing file (code %d). Could not save file',
          [GetLastError]);
    except
      DeleteFile(TempFN);
      raise;
    end;
    { Rename temporary file.
      Note: This is outside the try..except because we already deleted the
      existing file, and don't want the temp file also deleted in the unlikely
      event that the rename fails. }
    if not RenameFile(TempFN, FN) then
      raise Exception.CreateFmt('Error renaming temporary file (code %d). Could not save file',
        [GetLastError]);
    GetLastWriteTimeOfFile(FN, FFileLastWriteTime);
  end;

var
  FN: String;
begin
  Result := False;
  if SaveAs or (FFilename = '') then begin
    FN := FFilename;
    if not NewGetSaveFileName('', FN, '', SCompilerOpenFilter, 'iss', Handle) then Exit;
    FN := PathExpand(FN);
    SaveTo(FN);
    FFilename := FN;
    UpdateCaption;
  end
  else
    SaveTo(FFilename);
  Memo.Modified := False;
  if not FOptions.UndoAfterSave then
    Memo.ClearUndo;
  Result := True;
  ModifyMRUList(FFilename, True);
end;

function TCompileForm.ConfirmCloseFile(const PromptToSave: Boolean): Boolean;
var
  FileTitle: String;
begin
  Result := True;
  if FCompiling then begin
    MsgBox('Please stop the compile process before performing this command.',
      SCompilerFormCaption, mbError, MB_OK);
    Result := False;
    Exit;
  end;
  if FDebugging and not AskToDetachDebugger then begin
    Result := False;
    Exit;
  end;
  if PromptToSave and Memo.Modified then begin
    FileTitle := FFilename;
    if FileTitle = '' then FileTitle := 'Untitled';
    case MsgBox('The text in the ' + FileTitle + ' file has changed.'#13#10#13#10 +
       'Do you want to save the changes?', SCompilerFormCaption, mbError,
       MB_YESNOCANCEL) of
      IDYES: Result := SaveFile(False);
      IDNO: ;
    else
      Result := False;
    end;
  end;
end;

procedure TCompileForm.ReadMRUList;
{ Loads the list of MRU items from the registry }
var
  Ini: TConfigIniFile;
  I: Integer;
  S: String;
begin
  try
    Ini := TConfigIniFile.Create;
    try
      FMRUList.Clear;
      for I := 0 to High(FMRUMenuItems) do begin
        S := Ini.ReadString('ScriptFileHistoryNew', 'History' + IntToStr(I), '');
        if S <> '' then FMRUList.Add(S);
      end;
    finally
      Ini.Free;
    end;
  except
    { Ignore any exceptions; don't want to hold up the display of the
      File menu. }
  end;
end;

procedure TCompileForm.ModifyMRUList(const AFilename: String;
  const AddNewItem: Boolean);
var
  I: Integer;
  Ini: TConfigIniFile;
  S: String;
begin
  try
    { Load most recent items first, just in case they've changed }
    ReadMRUList;

    I := 0;
    while I < FMRUList.Count do begin
      if PathCompare(FMRUList[I], AFilename) = 0 then
        FMRUList.Delete(I)
      else
        Inc(I);
    end;
    if AddNewItem then
      FMRUList.Insert(0, AFilename);
    while FMRUList.Count > High(FMRUMenuItems)+1 do
      FMRUList.Delete(FMRUList.Count-1);

    { Save new MRU items }
    Ini := TConfigIniFile.Create;
    try
      { MRU list }
      for I := 0 to High(FMRUMenuItems) do begin
        if I < FMRUList.Count then
          S := FMRUList[I]
        else
          S := '';
        Ini.WriteString('ScriptFileHistoryNew', 'History' + IntToStr(I), S);
      end;
    finally
      Ini.Free;
    end;
  except
    { Handle exceptions locally; failure to save the MRU list should not be
      a fatal error. }
    Application.HandleException(Self);
  end;
end;

procedure TCompileForm.StatusMessage(const S: String);
var
  DC: HDC;
  Size: TSize;
begin
  with CompilerOutputList do begin
    try
      TopIndex := Items.Add(S);
    except
      on EOutOfResources do begin
        Clear;
        SendMessage(Handle, LB_SETHORIZONTALEXTENT, 0, 0);
        Items.Add(SCompilerStatusReset);
        TopIndex := Items.Add(S);
      end;
    end;
    DC := GetDC(0);
    try
      SelectObject(DC, Font.Handle);
      GetTextExtentPoint(DC, PChar(S), Length(S), Size);
    finally
      ReleaseDC(0, DC);
    end;
    Inc(Size.cx, 5);
    if Size.cx > SendMessage(Handle, LB_GETHORIZONTALEXTENT, 0, 0) then
      SendMessage(Handle, LB_SETHORIZONTALEXTENT, Size.cx, 0);
    Update;
  end;
end;

procedure TCompileForm.DebugLogMessage(const S: String);
var
  ST: TSystemTime;
  FirstLine: Boolean;

  procedure AddLine(S: String);
  var
    StartsWithTab: Boolean;
    DC: HDC;
    Size: TSize;
  begin
    if FirstLine then begin
      FirstLine := False;
      Insert(Format('[%.2u%s%.2u%s%.2u%s%.3u]   ', [ST.wHour, TimeSeparator,
        ST.wMinute, TimeSeparator, ST.wSecond, DecimalSeparator,
        ST.wMilliseconds]), S, 1);
      StartsWithTab := False;
    end
    else begin
      Insert(#9, S, 1);
      StartsWithTab := True;
    end;
    try
      DebugOutputList.TopIndex := DebugOutputList.Items.Add(S);
    except
      on EOutOfResources do begin
        DebugOutputList.Clear;
        SendMessage(DebugOutputList.Handle, LB_SETHORIZONTALEXTENT, 0, 0);
        DebugOutputList.Items.Add(SCompilerStatusReset);
        DebugOutputList.TopIndex := DebugOutputList.Items.Add(S);
      end;
    end;
    DC := GetDC(0);
    try
      SelectObject(DC, DebugOutputList.Font.Handle);
      if StartsWithTab then
        GetTextExtentPoint(DC, PChar(S)+1, Length(S)-1, Size)
      else
        GetTextExtentPoint(DC, PChar(S), Length(S), Size);
    finally
      ReleaseDC(0, DC);
    end;
    Inc(Size.cx, 5);
    if StartsWithTab then
      Inc(Size.cx, FDebugLogListTimeWidth);
    if Size.cx > SendMessage(DebugOutputList.Handle, LB_GETHORIZONTALEXTENT, 0, 0) then
      SendMessage(DebugOutputList.Handle, LB_SETHORIZONTALEXTENT, Size.cx, 0);
  end;

var
  LineStart, I: Integer;
  LastWasCR: Boolean;
begin
  GetLocalTime(ST);
  FirstLine := True;
  LineStart := 1;
  LastWasCR := False;
  { Call AddLine for each line. CR, LF, and CRLF line breaks are supported. }
  for I := 1 to Length(S) do begin
    if S[I] = #13 then begin
      AddLine(Copy(S, LineStart, I - LineStart));
      LineStart := I + 1;
      LastWasCR := True;
    end
    else begin
      if S[I] = #10 then begin
        if not LastWasCR then
          AddLine(Copy(S, LineStart, I - LineStart));
        LineStart := I + 1;
      end;
      LastWasCR := False;
    end;
  end;
  AddLine(Copy(S, LineStart, Maxint));
  DebugOutputList.Update;
end;

type
  PAppData = ^TAppData;
  TAppData = record
    Form: TCompileForm;
    ScriptFile: TTextFileReader;
    CurLineNumber: Integer;
    CurLine: String;
    OutputExe: String;
    ErrorMsg: String;
    ErrorFilename: String;
    ErrorLine: Integer;
    Aborted: Boolean;
  end;

function CompilerCallbackProc(Code: Integer; var Data: TCompilerCallbackData;
  AppData: Longint): Integer; stdcall;
begin
  Result := iscrSuccess;
  with PAppData(AppData)^ do
    case Code of
      iscbReadScript:
        if ScriptFile = nil then begin
          if Data.Reset then
            CurLineNumber := 0;
          if CurLineNumber < Form.Memo.Lines.Count then begin
            CurLine := Form.Memo.Lines[CurLineNumber];
            Data.LineRead := PChar(CurLine);
            Inc(CurLineNumber);
          end;
        end
        else begin
          { Note: In Inno Setup 3.0.1 and later we can ignore Data.Reset since
            it is only True once (when reading the first line). }
          if not ScriptFile.Eof then begin
            CurLine := ScriptFile.ReadLine;
            Data.LineRead := PChar(CurLine);
          end;
        end;
      iscbNotifyStatus:
        Form.StatusMessage(Data.StatusMsg);
      iscbNotifyIdle:
        begin
          Form.UpdateCompileStatusPanels(Data.CompressProgress,
            Data.CompressProgressMax, Data.SecondsRemaining,
            Data.BytesCompressedPerSecond);
          { We have to use HandleMessage instead of ProcessMessages so that
            Application.Idle is called. Otherwise, Flat TSpeedButton's don't
            react to the mouse being moved over them.
            Unfortunately, HandleMessage by default calls WaitMessage. To avoid
            this we have an Application.OnIdle handler which sets Done to False
            while compiling is in progress - see AppOnIdle.
            The GetQueueStatus check below is just an optimization; calling
            HandleMessage when there are no messages to process wastes CPU. }
          if GetQueueStatus(QS_ALLINPUT) <> 0 then begin
            Form.FBecameIdle := False;
            repeat
              Application.HandleMessage;
              { AppOnIdle sets FBecameIdle to True when it's called, which
                indicates HandleMessage didn't find any message to process }
            until Form.FBecameIdle;
          end;
          if Form.FCompileWantAbort then
            Result := iscrRequestAbort;
        end;
      iscbNotifySuccess:
        begin
          OutputExe := Data.OutputExeFilename;
          if Form.FCompilerVersion.BinVersion >= $3000001 then begin
            Form.ParseDebugInfo(Data.DebugInfo);
            Form.Memo.Invalidate;
          end;
        end;
      iscbNotifyError:
        begin
          if Assigned(Data.ErrorMsg) then
            ErrorMsg := Data.ErrorMsg
          else
            Aborted := True;
          ErrorFilename := Data.ErrorFilename;
          ErrorLine := Data.ErrorLine;
        end;
    end;
end;

procedure TCompileForm.CompileFile(AFilename: String;
  const ReadFromFile: Boolean);
var
  F: TTextFileReader;
  SourcePath, S, Options: String;
  Params: TCompileScriptParamsEx;
  AppData: TAppData;
  StartTime, ElapsedTime, ElapsedSeconds: DWORD;
  I: Integer;
begin
  if FCompiling then begin
    { Shouldn't get here, but just in case... }
    MsgBox('A compile is already in progress.', SCompilerFormCaption, mbError, MB_OK);
    Abort;
  end;

  if not ReadFromFile then begin
    if FOptions.Autosave and Memo.Modified then begin
      if not SaveFile(False) then Abort;
    end else if FFilename = '' then begin
      case MsgBox('Would you like to save the script before compiling?' +
         SNewLine2 + 'If you answer No, the compiled installation will be ' +
         'placed under your My Documents folder by default.',
         SCompilerFormCaption, mbConfirmation, MB_YESNOCANCEL) of
        IDYES: if not SaveFile(False) then Abort;
        IDNO: ;
      else
        Abort;
      end;
    end;
    AFilename := FFilename;
  end;

  DestroyDebugInfo;
  Memo.Invalidate;
  if ReadFromFile then
    F := TTextFileReader.Create(AFilename, fdOpenExisting, faRead, fsRead)
  else
    F := nil;
  try
    FBuildAnimationFrame := 0;
    FProgress := 0;
    FProgressMax := 0;

    Memo.Cursor := crAppStart;
    CompilerOutputList.Cursor := crAppStart;
    Memo.ReadOnly := True;
    HideError;
    CompilerOutputList.Clear;
    SendMessage(CompilerOutputList.Handle, LB_SETHORIZONTALEXTENT, 0, 0);
    DebugOutputList.Clear;
    SendMessage(DebugOutputList.Handle, LB_SETHORIZONTALEXTENT, 0, 0);
    TabSet.TabIndex := tiCompilerOutput;
    SetStatusPanelVisible(True);

    if AFilename <> '' then
      SourcePath := PathExtractPath(AFilename)
    else begin
      { If the script was not saved, default to My Documents }
      SourcePath := GetShellFolderPath(CSIDL_PERSONAL);
      if SourcePath = '' then
        raise Exception.Create('GetShellFolderPath failed');
    end;
    FillChar(Params, SizeOf(Params), 0);
    Params.Size := SizeOf(Params);
    Params.CompilerPath := nil;
    Params.SourcePath := PChar(SourcePath);
    Params.CallbackProc := CompilerCallbackProc;
    Pointer(Params.AppData) := @AppData;
    Options := '';
    for I := 0 to FSignTools.Count-1 do
      Options := Options + 'SignTool-' + FSignTools[I] + #0;
    Params.Options := PChar(Options);

    AppData.Form := Self;
    AppData.ScriptFile := F;
    AppData.CurLineNumber := 0;
    AppData.Aborted := False;

    StartTime := GetTickCount;
    StatusMessage(Format(SCompilerStatusStarting, [TimeToStr(Time)]));
    StatusMessage('');
    FCompiling := True;
    FCompileWantAbort := False;
    UpdateRunMenu;
    UpdateCaption;
    SetLowPriority(FOptions.LowPriorityDuringCompile);
    {$IFNDEF STATICCOMPILER}
    if ISDllCompileScript(Params) <> isceNoError then begin
    {$ELSE}
    if ISCompileScript(Params, False) <> isceNoError then begin
    {$ENDIF}
      StatusMessage(SCompilerStatusErrorAborted);
      if (AppData.ScriptFile = nil) and (AppData.ErrorLine > 0) and
         (AppData.ErrorFilename = '') then begin
        { Move the caret to the line number the error occured on }
        MoveCaret(AppData.ErrorLine, False);
        SetErrorLine(AppData.ErrorLine);
      end;
      if not AppData.Aborted then begin
        S := '';
        if AppData.ErrorFilename <> '' then
          S := 'File: ' + AppData.ErrorFilename + SNewLine2;
        if AppData.ErrorLine > 0 then
          S := S + Format('Line %d:' + SNewLine, [AppData.ErrorLine]);
        S := S + AppData.ErrorMsg;
        if FTaskbarList3 <> nil then
          FTaskbarList3.SetProgressState(Application.Handle, TBPF_ERROR);
        MsgBox(S, 'Compiler Error', mbCriticalError, MB_OK)
      end;
      Abort;
    end;
    ElapsedTime := GetTickCount - StartTime;
    ElapsedSeconds := ElapsedTime div 1000;
    StatusMessage(Format(SCompilerStatusFinished, [TimeToStr(Time),
      Format('%.2u%s%.2u%s%.3u', [ElapsedSeconds div 60, TimeSeparator,
        ElapsedSeconds mod 60, DecimalSeparator, ElapsedTime mod 1000])]));
  finally
    F.Free;
    FCompiling := False;
    SetLowPriority(False);
    Memo.Cursor := crIBeam;
    CompilerOutputList.Cursor := crDefault;
    Memo.ReadOnly := False;
    UpdateRunMenu;
    UpdateCaption;
    InvalidateStatusPanel(spCompileIcon);
    InvalidateStatusPanel(spCompileProgress);
    if FTaskbarList3 <> nil then
      FTaskbarList3.SetProgressState(Application.Handle, TBPF_NOPROGRESS);
    StatusBar.Panels[spExtraStatus].Text := '';
  end;
  FCompiledExe := AppData.OutputExe;
  FModifiedSinceLastCompile := False;
  FModifiedSinceLastCompileAndGo := False;
end;

procedure TCompileForm.SetLowPriority(ALowPriority: Boolean);
begin
  if ALowPriority then begin
    { Save current priority and change to 'low' }
    if FSavePriorityClass = 0 then
      FSavePriorityClass := GetPriorityClass(GetCurrentProcess);
    SetPriorityClass(GetCurrentProcess, IDLE_PRIORITY_CLASS);
  end
  else begin
    { Restore original priority }
    if FSavePriorityClass <> 0 then begin
      SetPriorityClass(GetCurrentProcess, FSavePriorityClass);
      FSavePriorityClass := 0;
    end;
  end;
end;

procedure TCompileForm.SyncEditorOptions;
begin
  if FOptions.UseSyntaxHighlighting then
    Memo.Highlighter := SynMultiSyn1
  else
    Memo.Highlighter := nil;

  if FOptions.CursorPastEOL then
    Memo.Options := Memo.Options + [eoScrollPastEol]
  else
    Memo.Options := Memo.Options - [eoScrollPastEol];

  Memo.TabWidth := FOptions.TabWidth;
end;

procedure TCompileForm.FMenuClick(Sender: TObject);

  function DoubleAmp(const S: String): String;
  var
    I: Integer;
  begin
    Result := S;
    I := 1;
    while I <= Length(Result) do begin
      if Result[I] = '&' then begin
        Inc(I);
        Insert('&', Result, I);
        Inc(I);
      end
      else
        Inc(I, PathCharLength(S, I));
    end;
  end;

var
  I: Integer;
begin
  ReadMRUList;
  FMRUSep.Visible := FMRUList.Count <> 0;
  for I := 0 to High(FMRUMenuItems) do
    with FMRUMenuItems[I] do begin
      if I < FMRUList.Count then begin
        Visible := True;
        Caption := '&' + IntToStr((I+1) mod 10) + ' ' + DoubleAmp(FMRUList[I]);
      end
      else
        Visible := False;
    end;
end;

procedure TCompileForm.FNewClick(Sender: TObject);
begin
  if ConfirmCloseFile(True) then
    NewFile;
end;

procedure TCompileForm.FNewWizardClick(Sender: TObject);
begin
  if ConfirmCloseFile(True) then
    NewWizardFile;
end;

procedure TCompileForm.ShowOpenDialog(const Examples: Boolean);
var
  InitialDir, FileName: String;
begin
  if Examples then begin
    InitialDir := PathExtractPath(NewParamStr(0)) + 'Examples';
    Filename := PathExtractPath(NewParamStr(0)) + 'Examples\Example1.iss';
  end
  else begin
    InitialDir := '';
    Filename := '';
  end;
  if ConfirmCloseFile(True) then
    if NewGetOpenFileName('', FileName, InitialDir, SCompilerOpenFilter, 'iss', Handle) then
      OpenFile(Filename, False);
end;

procedure TCompileForm.FOpenClick(Sender: TObject);
begin
  ShowOpenDialog(False);
end;

procedure TCompileForm.FSaveClick(Sender: TObject);
begin
  SaveFile(False);
end;

procedure TCompileForm.FSaveAsClick(Sender: TObject);
begin
  SaveFile(True);
end;

procedure TCompileForm.FMRUClick(Sender: TObject);
var
  I: Integer;
begin
  if ConfirmCloseFile(True) then
    for I := 0 to High(FMRUMenuItems) do
      if FMRUMenuItems[I] = Sender then begin
        OpenMRUFile(FMRUList[I]);
        Break;
      end;
end;

procedure TCompileForm.FExitClick(Sender: TObject);
begin
  Close;
end;

procedure TCompileForm.EMenuClick(Sender: TObject);
begin
  EUndo.Enabled := Memo.CanUndo;
  ERedo.Enabled := Memo.CanRedo;
  ECut.Enabled := Memo.SelAvail;
  ECopy.Enabled := Memo.SelAvail;
  EPaste.Enabled := Clipboard.HasFormat(CF_TEXT);
  EDelete.Enabled := Memo.SelAvail;
end;

procedure TCompileForm.PopupMenu1Popup(Sender: TObject);
begin
  PUndo.Enabled := Memo.CanUndo;
  PRedo.Enabled := Memo.CanRedo;
  PCut.Enabled := Memo.SelAvail;
  PCopy.Enabled := Memo.SelAvail;
  PPaste.Enabled := Clipboard.HasFormat(CF_TEXT);
  PDelete.Enabled := Memo.SelAvail;
end;

procedure TCompileForm.EUndoClick(Sender: TObject);
begin
  Memo.Undo;
end;

procedure TCompileForm.ERedoClick(Sender: TObject);
begin
  Memo.Redo;
end;

procedure TCompileForm.ECutClick(Sender: TObject);
begin
  Memo.CutToClipboard;
end;

procedure TCompileForm.ECopyClick(Sender: TObject);
begin
  Memo.CopyToClipboard;
end;

procedure TCompileForm.EPasteClick(Sender: TObject);
begin
  Memo.PasteFromClipboard;
end;

procedure TCompileForm.EDeleteClick(Sender: TObject);
begin
  Memo.ClearSelection;
end;

procedure TCompileForm.ESelectAllClick(Sender: TObject);
begin
  Memo.SelectAll;
end;

procedure TCompileForm.VMenuClick(Sender: TObject);
begin
  VToolbar.Checked := ToolbarPanel.Visible;
  VStatusBar.Checked := StatusBar.Visible;
  VHide.Checked := not StatusPanel.Visible;
  VCompilerOutput.Checked := StatusPanel.Visible and (TabSet.TabIndex = tiCompilerOutput);
  VDebugOutput.Checked := StatusPanel.Visible and (TabSet.TabIndex = tiDebugOutput);
end;

procedure TCompileForm.VToolbarClick(Sender: TObject);
begin
  ToolbarPanel.Visible := not ToolbarPanel.Visible;
end;

procedure TCompileForm.VStatusBarClick(Sender: TObject);
begin
  StatusBar.Visible := not StatusBar.Visible;
end;

procedure TCompileForm.SetStatusPanelVisible(const AVisible: Boolean);
var
  CaretWasInView: Boolean;
begin
  if StatusPanel.Visible <> AVisible then begin
    CaretWasInView := (Memo.CaretY >= Memo.TopLine) and
      (Memo.CaretY < Memo.TopLine + Memo.LinesInWindow);
    if AVisible then begin
      { Ensure the status panel height isn't out of range before showing }
      UpdateStatusPanelHeight(StatusPanel.Height);
      SplitPanel.Top := ClientHeight;
      StatusPanel.Top := ClientHeight;
    end;
    SplitPanel.Visible := AVisible;
    StatusPanel.Visible := AVisible;
    if AVisible and CaretWasInView then begin
      { If the caret was in view, make sure it still is }
      if Memo.CaretY >= Memo.TopLine + Memo.LinesInWindow then
        Memo.TopLine := Memo.CaretY - Memo.LinesInWindow + 1;
    end;
  end;
end;

procedure TCompileForm.VHideClick(Sender: TObject);
begin
  SetStatusPanelVisible(False);
end;

procedure TCompileForm.VCompilerOutputClick(Sender: TObject);
begin
  TabSet.TabIndex := tiCompilerOutput;
  SetStatusPanelVisible(True);
end;

procedure TCompileForm.VDebugOutputClick(Sender: TObject);
begin
  TabSet.TabIndex := tiDebugOutput;
  SetStatusPanelVisible(True);
end;

procedure TCompileForm.BMenuClick(Sender: TObject);
begin
  BLowPriority.Checked := FOptions.LowPriorityDuringCompile;
end;

procedure TCompileForm.BCompileClick(Sender: TObject);
begin
  CompileFile('', False);
end;

procedure TCompileForm.BStopCompileClick(Sender: TObject);
begin
  if FTaskbarList3 <> nil then
    FTaskbarList3.SetProgressState(Application.Handle, TBPF_PAUSED);
  try
    if MsgBox('Are you sure you want to abort the compile?', SCompilerFormCaption,
       mbConfirmation, MB_YESNO or MB_DEFBUTTON2) <> IDNO then
      FCompileWantAbort := True;
  finally
    if FTaskbarList3 <> nil then
      FTaskbarList3.SetProgressState(Application.Handle, TBPF_NORMAL);
  end;
end;

procedure TCompileForm.BLowPriorityClick(Sender: TObject);
begin
  FOptions.LowPriorityDuringCompile := not FOptions.LowPriorityDuringCompile;
  { If a compile is already in progress, change the priority now }
  if FCompiling then
    SetLowPriority(FOptions.LowPriorityDuringCompile);
end;

procedure TCompileForm.HMenuClick(Sender: TObject);
begin
  HISPPDoc.Visible := NewFileExists(PathExtractPath(NewParamStr(0)) + 'ispp.chm');
  HISPPWebsite.Visible := HISPPDoc.Visible;
  HISPPSep.Visible := HISPPDoc.Visible;
end;

function GetHelpFile: String;
begin
  Result := PathExtractPath(NewParamStr(0)) + 'isetup.chm';
end;

procedure TCompileForm.HDocClick(Sender: TObject);
var
  HelpFile: String;
begin
  HelpFile := GetHelpFile;
  if Assigned(HtmlHelp) then
    HtmlHelp(GetDesktopWindow, PChar(HelpFile), HH_DISPLAY_TOPIC, 0);
end;

procedure TCompileForm.MemoKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
var
  S, HelpFile: String;
  KLink: THH_AKLINK;
begin
  if Key = VK_F1 then begin
    HelpFile := GetHelpFile;
    if Assigned(HtmlHelp) then begin
      HtmlHelp(GetDesktopWindow, PChar(HelpFile), HH_DISPLAY_TOPIC, 0);
      S := Memo.WordAtCursor;
      if S <> '' then begin
        FillChar(KLink, SizeOf(KLink), 0);
        KLink.cbStruct := SizeOf(KLink);
        KLink.pszKeywords := PChar(S);
        KLink.fIndexOnFail := True;
        HtmlHelp(GetDesktopWindow, PChar(HelpFile), HH_KEYWORD_LOOKUP, DWORD(@KLink));
      end;
    end;
  end;
end;

procedure TCompileForm.HExamplesClick(Sender: TObject);
begin
  ShellExecute(Application.Handle, 'open',
    PChar(PathExtractPath(NewParamStr(0)) + 'Examples'), nil, nil, SW_SHOW);
end;

procedure TCompileForm.HFaqClick(Sender: TObject);
begin
  ShellExecute(Application.Handle, 'open',
    PChar(PathExtractPath(NewParamStr(0)) + 'isfaq.htm'), nil, nil, SW_SHOW);
end;

procedure TCompileForm.HWhatsNewClick(Sender: TObject);
begin
  ShellExecute(Application.Handle, 'open',
    PChar(PathExtractPath(NewParamStr(0)) + 'whatsnew.htm'), nil, nil, SW_SHOW);
end;

procedure TCompileForm.HWebsiteClick(Sender: TObject);
begin
  ShellExecute(Application.Handle, 'open', 'http://www.innosetup.com/', nil,
    nil, SW_SHOW);
end;

procedure TCompileForm.HPSWebsiteClick(Sender: TObject);
begin
  ShellExecute(Application.Handle, 'open', 'http://www.remobjects.com/ps', nil,
    nil, SW_SHOW);
end;

procedure TCompileForm.HISPPDocClick(Sender: TObject);
begin
  if Assigned(HtmlHelp) then
    HtmlHelp(GetDesktopWindow, PChar(GetHelpFile + '::/hh_isppredirect.xhtm'), HH_DISPLAY_TOPIC, 0);
end;

procedure TCompileForm.HISPPWebsiteClick(Sender: TObject);
begin
  ShellExecute(Application.Handle, 'open', 'http://ispp.sourceforge.net/', nil,
    nil, SW_SHOW);
end;

procedure TCompileForm.HDonateClick(Sender: TObject);
begin
  ShellExecute(Application.Handle, 'open', 'http://www.jrsoftware.org/isdonate.php', nil,
    nil, SW_SHOW);
end;

procedure TCompileForm.HAboutClick(Sender: TObject);
var
  S: String;
begin
  { Removing the About box or modifying any existing text inside it is a
    violation of the Inno Setup license agreement; see LICENSE.TXT.
    However, adding additional lines to the About box is permitted, as long as
    they are placed below the original copyright notice. }
  S := FCompilerVersion.Title + ' Compiler version ' +
    String(FCompilerVersion.Version) + SNewLine;
  if FCompilerVersion.Title <> 'Inno Setup' then
    S := S + (SNewLine + 'Based on Inno Setup' + SNewLine);
  S := S + ('Copyright (C) 1997-2010 Jordan Russell' + SNewLine +
    'Portions Copyright (C) 2000-2010 Martijn Laan' + SNewLine +
    'All rights reserved.' + SNewLine2 +
    'Inno Setup home page:' + SNewLine +
    'http://www.innosetup.com/' + SNewLine2 +
    'RemObjects Pascal Script home page:' + SNewLine +
    'http://www.remobjects.com/ps' + SNewLine2 +
    'Refer to LICENSE.TXT for conditions of distribution and use.');
  MsgBox(S, 'About ' + FCompilerVersion.Title, mbInformation, MB_OK);
end;

procedure TCompileForm.WMStartCommandLineCompile(var Message: TMessage);
var
  Code: Integer;
begin
  UpdateStatusPanelHeight(ClientHeight);
  Code := 0;
  try
    try
      CompileFile(CommandLineFilename, True);
    except
      Code := 2;
      Application.HandleException(Self);
    end;
  finally
    Halt(Code);
  end;
end;

procedure TCompileForm.WMStartCommandLineWizard(var Message: TMessage);
var
  Code: Integer;
begin
  Code := 0;
  try
    try
      NewWizardFile;
    except
      Code := 2;
      Application.HandleException(Self);
    end;
  finally
    Halt(Code);
  end;
end;

procedure TCompileForm.WMShowStartupForm(var Message: TMessage);
var
  StartupForm: TStartupForm;
  Ini: TConfigIniFile;
begin
  ReadMRUList;
  StartupForm := TStartupForm.Create(Application);
  try
    StartupForm.MRUList := FMRUList;
    StartupForm.StartupCheck.Checked := not FOptions.ShowStartupForm;
    if StartupForm.ShowModal = mrOK then begin
      if FOptions.ShowStartupForm <> not StartupForm.StartupCheck.Checked then begin
        FOptions.ShowStartupForm := not StartupForm.StartupCheck.Checked;
        Ini := TConfigIniFile.Create;
        try
          Ini.WriteBool('Options', 'ShowStartupForm', FOptions.ShowStartupForm);
        finally
          Ini.Free;
        end;
      end;
      case StartupForm.Result of
        srEmpty:
          FNewClick(Self);
        srWizard:
          FNewWizardClick(Self);
        srOpenFile:
          if ConfirmCloseFile(True) then
            OpenMRUFile(StartupForm.ResultFileName);
        srOpenDialog:
          ShowOpenDialog(False);
        srOpenDialogExamples:
          ShowOpenDialog(True);
      end;
    end;
  finally
    StartupForm.Free;
  end;
end;

procedure TCompileForm.InitializeFindText(Dlg: TFindDialog);
var
  S: String;
begin
  S := Memo.SelText;
  if (S <> '') and (Pos(#13, S) = 0) and (Pos(#10, S) = 0) then
    Dlg.FindText := S
  else
    Dlg.FindText := FLastFindText;
end;

procedure TCompileForm.EFindClick(Sender: TObject);
begin
  ReplaceDialog.CloseDialog;
  if FindDialog.Handle = 0 then
    InitializeFindText(FindDialog);
  FindDialog.Execute;
end;

procedure TCompileForm.EFindNextClick(Sender: TObject);
begin
  if FLastFindText = '' then
    EFindClick(Sender)
  else
    FindNext;
end;

function FindOptionsToSearchOptions(const FindOptions: TFindOptions): TSynSearchOptions;
begin
  Result := [];
  if frMatchCase in FindOptions then
    Include(Result, ssoMatchCase);
  if frWholeWord in FindOptions then
    Include(Result, ssoWholeWord);
  if not(frDown in FindOptions) then
    Include(Result, ssoBackwards);
end;

procedure TCompileForm.FindNext;
begin
  if Memo.SearchReplace(FLastFindText, '', FindOptionsToSearchOptions(FLastFindOptions)) = 0 then
    MsgBoxFmt('Cannot find "%s"', [FLastFindText], '', mbError, MB_OK);
end;

procedure TCompileForm.FindDialogFind(Sender: TObject);
begin
  { this event handler is shared between FindDialog & ReplaceDialog }
  with Sender as TFindDialog do begin
    { Save a copy of the current text so that InitializeFindText doesn't
      mess up the operation of Edit | Find Next }
    FLastFindOptions := Options;
    FLastFindText := FindText;
  end;
  FindNext;
end;

procedure TCompileForm.EReplaceClick(Sender: TObject);
begin
  FindDialog.CloseDialog;
  if ReplaceDialog.Handle = 0 then begin
    InitializeFindText(ReplaceDialog);
    ReplaceDialog.ReplaceText := FLastReplaceText;
  end;
  ReplaceDialog.Execute;
end;

procedure TCompileForm.ReplaceDialogReplace(Sender: TObject);
var
  Same: Boolean;
begin
  with ReplaceDialog do begin
    FLastFindOptions := Options;
    FLastFindText := FindText;
    FLastReplaceText := ReplaceText;

    if not(frMatchCase in Options) then
      Same := AnsiCompareText(Memo.SelText, FindText) = 0
    else
      Same := Memo.SelText = FindText;
    if Same then
      Memo.SelText := ReplaceText;

    if Memo.SearchReplace(FindText, '', FindOptionsToSearchOptions(Options)) = 0 then
      MsgBoxFmt('Cannot find "%s"', [FindText], '', mbError, MB_OK)
    else
      if frReplaceAll in Options then begin
        repeat
          Memo.SelText := ReplaceText;
        until Memo.SearchReplace(FindText, '', FindOptionsToSearchOptions(Options)) = 0;
      end;
  end;
end;

procedure TCompileForm.UpdateStatusPanelHeight(H: Integer);
var
  MinHeight, MaxHeight: Integer;
begin
  MinHeight := (3 * DebugOutputList.ItemHeight + 4) +
    SpacerPaintBox.Height + TabSet.Height;
  MaxHeight := BodyPanel.ClientHeight - 48 - SplitPanel.Height;
  if H > MaxHeight then H := MaxHeight;
  if H < MinHeight then H := MinHeight;
  StatusPanel.Height := H;
end;

procedure TCompileForm.SplitPanelMouseMove(Sender: TObject;
  Shift: TShiftState; X, Y: Integer);
begin
  if (ssLeft in Shift) and StatusPanel.Visible then begin
    UpdateStatusPanelHeight(BodyPanel.ClientToScreen(Point(0, 0)).Y -
      SplitPanel.ClientToScreen(Point(0, Y)).Y +
      BodyPanel.ClientHeight - (SplitPanel.Height div 2));
  end;
end;

procedure TCompileForm.TAddRemoveProgramsClick(Sender: TObject);
var
  Dir: String;
  Wow64DisableWow64FsRedirectionFunc: function(var OldValue: Pointer): BOOL; stdcall;
  Wow64RevertWow64FsRedirectionFunc: function(OldValue: Pointer): BOOL; stdcall;
  RedirDisabled: Boolean;
  RedirOldValue: Pointer;
  StartupInfo: TStartupInfo;
  ProcessInfo: TProcessInformation;
begin
  if Win32Platform = VER_PLATFORM_WIN32_NT then
    Dir := GetSystemDir
  else
    Dir := GetWinDir;

  FillChar(StartupInfo, SizeOf(StartupInfo), 0);
  StartupInfo.cb := SizeOf(StartupInfo);
  { Have to disable file system redirection because the 32-bit version of
    appwiz.cpl is buggy on XP x64 RC2 -- it doesn't show any Change/Remove
    buttons on 64-bit MSI entries, and it doesn't list non-MSI 64-bit apps
    at all. }
  Wow64DisableWow64FsRedirectionFunc := GetProcAddress(GetModuleHandle(kernel32),
    'Wow64DisableWow64FsRedirection');
  Wow64RevertWow64FsRedirectionFunc := GetProcAddress(GetModuleHandle(kernel32),
    'Wow64RevertWow64FsRedirection');
  RedirDisabled := Assigned(Wow64DisableWow64FsRedirectionFunc) and
    Assigned(Wow64RevertWow64FsRedirectionFunc) and
    Wow64DisableWow64FsRedirectionFunc(RedirOldValue);
  try
    Win32Check(CreateProcess(nil, PChar('"' + AddBackslash(Dir) + 'control.exe" appwiz.cpl'),
       nil, nil, False, 0, nil, PChar(Dir), StartupInfo, ProcessInfo));
  finally
    if RedirDisabled then
      Wow64RevertWow64FsRedirectionFunc(RedirOldValue);
  end;
  CloseHandle(ProcessInfo.hProcess);
  CloseHandle(ProcessInfo.hThread);
end;

procedure TCompileForm.TGenerateGUIDClick(Sender: TObject);
begin
  if MsgBox('The generated GUID will be inserted into the editor at the cursor position. Continue?',
     SCompilerFormCaption, mbConfirmation, MB_YESNO) = IDYES then
    Memo.SelText := GenerateGuid;
end;

procedure TCompileForm.TSignToolsClick(Sender: TObject);
var
  SignToolsForm: TSignToolsForm;
  Ini: TConfigIniFile;
  I: Integer;
begin
  SignToolsForm := TSignToolsForm.Create(Application);
  try
    SignToolsForm.SignTools := FSignTools;

    if SignToolsForm.ShowModal <> mrOK then
      Exit;

    FSignTools.Assign(SignToolsForm.SignTools);

    { Save new options }
    Ini := TConfigIniFile.Create;
    try
      Ini.EraseSection('SignTools');
      for I := 0 to FSignTools.Count-1 do
        Ini.WriteString('SignTools', 'SignTool' + IntToStr(I), FSignTools[I]);
    finally
      Ini.Free;
    end;
  finally
    SignToolsForm.Free;
  end;
end;

procedure TCompileForm.TOptionsClick(Sender: TObject);
var
  OptionsForm: TOptionsForm;
  Ini: TConfigIniFile;
begin
  OptionsForm := TOptionsForm.Create(Application);
  try
    OptionsForm.StartupCheck.Checked := FOptions.ShowStartupForm;
    OptionsForm.WizardCheck.Checked := FOptions.UseWizard;
    OptionsForm.AutosaveCheck.Checked := FOptions.Autosave;
    OptionsForm.BackupCheck.Checked := FOptions.MakeBackups;
    OptionsForm.FullPathCheck.Checked := FOptions.FullPathInTitleBar;
    OptionsForm.UndoAfterSaveCheck.Checked := FOptions.UndoAfterSave;
    OptionsForm.PauseOnDebuggerExceptionsCheck.Checked := FOptions.PauseOnDebuggerExceptions;
    OptionsForm.RunAsDifferentUserCheck.Checked := FOptions.RunAsDifferentUser;
    OptionsForm.UseSynHighCheck.Checked := FOptions.UseSyntaxHighlighting;
    OptionsForm.CursorPastEOLCheck.Checked := FOptions.CursorPastEOL;
    OptionsForm.TabWidthEdit.Text := IntToStr(FOptions.TabWidth);
    OptionsForm.FontPanel.Font.Assign(Memo.Font);

    if OptionsForm.ShowModal <> mrOK then
      Exit;

    FOptions.ShowStartupForm := OptionsForm.StartupCheck.Checked;
    FOptions.UseWizard := OptionsForm.WizardCheck.Checked;
    FOptions.Autosave := OptionsForm.AutosaveCheck.Checked;
    FOptions.MakeBackups := OptionsForm.BackupCheck.Checked;
    FOptions.FullPathInTitleBar := OptionsForm.FullPathCheck.Checked;
    FOptions.UndoAfterSave := OptionsForm.UndoAfterSaveCheck.Checked;
    FOptions.PauseOnDebuggerExceptions := OptionsForm.PauseOnDebuggerExceptionsCheck.Checked;
    FOptions.RunAsDifferentUser := OptionsForm.RunAsDifferentUserCheck.Checked;
    FOptions.UseSyntaxHighlighting := OptionsForm.UseSynHighCheck.Checked;
    FOptions.CursorPastEOL := OptionsForm.CursorPastEOLCheck.Checked;
    FOptions.TabWidth := StrToInt(OptionsForm.TabWidthEdit.Text);
    UpdateCaption;
    SyncEditorOptions;
    Memo.Font.Assign(OptionsForm.FontPanel.Font);
    UpdateNewButtons;

    { Save new options }
    Ini := TConfigIniFile.Create;
    try
      Ini.WriteBool('Options', 'ShowStartupForm', FOptions.ShowStartupForm);
      Ini.WriteBool('Options', 'UseWizard', FOptions.UseWizard);
      Ini.WriteBool('Options', 'Autosave', FOptions.Autosave);
      Ini.WriteBool('Options', 'MakeBackups', FOptions.MakeBackups);
      Ini.WriteBool('Options', 'FullPathInTitleBar', FOptions.FullPathInTitleBar);
      Ini.WriteBool('Options', 'UndoAfterSave', FOptions.UndoAfterSave);
      Ini.WriteBool('Options', 'PauseOnDebuggerExceptions', FOptions.PauseOnDebuggerExceptions);
      Ini.WriteBool('Options', 'RunAsDifferentUser', FOptions.RunAsDifferentUser);
      Ini.WriteBool('Options', 'UseSynHigh', FOptions.UseSyntaxHighlighting);
      Ini.WriteBool('Options', 'EditorCursorPastEOL', FOptions.CursorPastEOL);
      Ini.WriteInteger('Options', 'TabWidth', FOptions.TabWidth);
      Ini.WriteString('Options', 'EditorFontName', Memo.Font.Name);
      Ini.WriteInteger('Options', 'EditorFontSize', Memo.Font.Size);
      Ini.WriteInteger('Options', 'EditorFontCharset', Memo.Font.Charset);
    finally
      Ini.Free;
    end;
  finally
    OptionsForm.Free;
  end;
end;

procedure TCompileForm.MoveCaret(const LineNumber: Integer;
  const AlwaysResetColumn: Boolean);
begin
  { Clear any selection }
  Memo.BlockBegin := Memo.CaretXY;

  { If the line isn't in view, scroll so that it's in the center }
  if (LineNumber < Memo.TopLine) or
     (LineNumber >= Memo.TopLine + Memo.LinesInWindow) then
    Memo.TopLine := LineNumber - Memo.LinesInWindow div 2;

  if AlwaysResetColumn or (Memo.CaretY <> LineNumber) then
    Memo.CaretXY := {$IFDEF UNICODE}BufferCoord(1, LineNumber){$ELSE}Point(1, LineNumber){$ENDIF};
  Memo.SetFocus;
end;

procedure TCompileForm.SetErrorLine(ALine: Integer);
begin
  if FErrorLine <> ALine then begin
    if FErrorLine > 0 then
      Memo.InvalidateLine(FErrorLine);
    FErrorLine := ALine;
    if FErrorLine > 0 then
      Memo.InvalidateLine(FErrorLine);
  end;
end;

procedure TCompileForm.SetStepLine(ALine: Integer);
begin
  if FStepLine <> ALine then begin
    if FStepLine > 0 then
      Memo.InvalidateLine(FStepLine);
    FStepLine := ALine;
    if FStepLine > 0 then
      Memo.InvalidateLine(FStepLine);
  end;
end;

procedure TCompileForm.HideError;
begin
  SetErrorLine(0);
  if not FCompiling then
    StatusBar.Panels[spExtraStatus].Text := '';
end;

procedure TCompileForm.MemoStatusChange(Sender: TObject;
  Changes: TSynStatusChanges);
const
  InsertText: array[Boolean] of String = ('Overwrite', 'Insert');
begin
  if (scCaretX in Changes) or (scCaretY in Changes) then begin
    HideError;
    StatusBar.Panels[spCaretPos].Text := Format('%4d:%4d', [Memo.CaretY, Memo.CaretX]);
  end;
  if scModified in Changes then begin
    if Memo.Modified then
      StatusBar.Panels[spModified].Text := 'Modified'
    else
      StatusBar.Panels[spModified].Text := '';
  end;
  if (scInsertMode in Changes) or (scReadOnly in Changes) then begin
    if Memo.ReadOnly then
      StatusBar.Panels[spInsertMode].Text := 'Read only'
    else
      StatusBar.Panels[spInsertMode].Text := InsertText[Memo.InsertMode];
  end;
end;

procedure TCompileForm.MemoChange(Sender: TObject);
begin
  FModifiedSinceLastCompile := True;
  if FDebugging then
    FModifiedSinceLastCompileAndGo := True
  else begin
    { Modified while not debugging; free the debug info and clear the dots }
    if Assigned(FLineState) then
      Memo.Invalidate;
    DestroyDebugInfo;
  end;
  { Need HideError here because we don't get an OnStatusChange event when the
    Delete key is pressed }
  HideError;
end;

procedure TCompileForm.MemoSpecialLineColors(Sender: TObject;
  Line: Integer; var Special: Boolean; var FG, BG: TColor);
begin
  if FErrorLine = Line then begin
    Special := True;
    FG := clWhite;
    BG := clMaroon;
  end
  else if FStepLine = Line then begin
    Special := True;
    FG := clWhite;
    BG := clBlue;
  end
  else if FBreakPoints.IndexOf(Pointer(Line)) <> -1 then begin
    Special := True;
    if not Assigned(FLineState) or ((Line <= FLineStateCount) and (FLineState[Line] <> lnUnknown)) then begin
      FG := clWhite;
      BG := clRed;
    end else begin
      FG := clLime;
      BG := clOlive;
    end;
  end;
end;

procedure TCompileForm.MemoPaint(Sender: TObject; ACanvas: TCanvas);
{ Draws the gutter dots }
var
  CR: TRect;
  H, Y, I, ImgIndex: Integer;
begin
  H := Memo.LineHeight;
  Y := 0;
  CR := ACanvas.ClipRect;
  for I := Memo.TopLine to Memo.Lines.Count do begin
    if Y >= CR.Bottom then
      Break;
    if Y + H > CR.Top then begin
      if FBreakPoints.IndexOf(Pointer(I)) <> -1 then begin
        if not Assigned(FLineState) then
          ImgIndex := 0
        else if (I <= FLineStateCount) and (FLineState[I] <> lnUnknown) then
          ImgIndex := 1
        else
          ImgIndex := 2;
        ImageList_Draw(FGutterImageList, ImgIndex, ACanvas.Handle, 5,
          Y + (H div 2) - 5, ILD_NORMAL);
      end
      else if Assigned(FLineState) and (I <= FLineStateCount) and (FLineState[I] <> lnUnknown) then begin
        case FLineState[I] of
          lnHasEntry: begin
              ACanvas.Pen.Color := clGray;
              ACanvas.Brush.Color := clSilver;
            end;
          lnEntryProcessed: begin
              ACanvas.Pen.Color := clGreen;
              ACanvas.Brush.Color := clLime;
            end;
        end;
        ACanvas.Rectangle(8, Y + (H div 2) - 3, 13, Y + (H div 2) + 2);
      end;
    end;
    Inc(Y, H);
  end;
end;

procedure TCompileForm.MemoWndProc(var Message: TMessage);

  function GetCodeVariableDebugEntryFromLineCol(Line, Col: Integer): PVariableDebugEntry;
  var
    I: Integer;
  begin
    Result := nil;
    for I := 0 to FVariableDebugEntriesCount-1 do begin
      if (FVariableDebugEntries[I].LineNumber = Line) and
         (FVariableDebugEntries[I].Col = Col) then begin
        Result := @FVariableDebugEntries[I];
        Break;
      end;
    end;
  end;

var
  Line, Col, I, J: Integer;
  P: {$IFDEF UNICODE}TDisplayCoord{$ELSE}TPoint{$ENDIF};
  P2: TPoint;
  S, Output: String;
  DebugEntry: PVariableDebugEntry;
begin
  if (Message.Msg = CM_HINTSHOW) and (FDebugClientWnd <> 0) then begin
    with TCMHintShow(Message).HintInfo^ do begin
{$IFDEF UNICODE}
      P := Memo.PixelsToRowColumn(CursorPos.X - Memo.CharWidth div 2,
        CursorPos.Y);
{$ELSE}
      P := Memo.PixelsToRowColumn(Point(CursorPos.X - Memo.CharWidth div 2,
        CursorPos.Y));
{$ENDIF}
      if P.{$IFDEF UNICODE}Row{$ELSE}Y{$ENDIF} <= Memo.Lines.Count then begin
        Line := P.{$IFDEF UNICODE}Row{$ELSE}Y{$ENDIF};
        Col := P.{$IFDEF UNICODE}Column{$ELSE}X{$ENDIF};
        S := Memo.Lines[Line-1];

        if (Col >= 1) and (Col <= Length(S)) and CharInSet(S[Col], ['a'..'z','A'..'Z','0'..'9','_']) then begin
          while (Col > 1) and CharInSet(S[Col-1], ['a'..'z','A'..'Z','0'..'9','_']) do
            Dec(Col);
          DebugEntry := GetCodeVariableDebugEntryFromLineCol(Line, Col);
          if DebugEntry <> nil then begin
            J := Col;
            while CharInSet(S[J], ['a'..'z','A'..'Z','0'..'9','_']) do
              Inc(J);
            case EvaluateVariableEntry(DebugEntry, Output) of
              1: HintStr := Output;
              2: HintStr := Output;
            else
              HintStr := 'Unknown error';
            end;
            P2 := Memo.RowColumnToPixels({$IFDEF UNICODE}DisplayCoord{$ELSE}Point{$ENDIF}(Col, P.{$IFDEF UNICODE}Row{$ELSE}Y{$ENDIF}));
            CursorRect := Bounds(P2.X, P2.Y, (J-Col) * Memo.CharWidth,
              Memo.LineHeight);
            HideTimeout := High(Integer);  { infinite }
            Exit;
          end;
        end;

        I := 1;
        while I <= Length(S) do begin
          if S[I] = '{' then begin
            if (I < Length(S)) and (S[I+1] = '{') then
              { Skip '{{' }
              Inc(I, 2)
            else begin
              J := SkipPastConst(S, I);
              if J = 0 then  { unclosed constant? }
                Break;
              if (P.{$IFDEF UNICODE}Column{$ELSE}X{$ENDIF} >= I) and (P.{$IFDEF UNICODE}Column{$ELSE}X{$ENDIF} < J) then begin
                HintStr := Copy(S, I, J-I);
                case EvaluateConstant(HintStr, Output) of
                  1: HintStr := HintStr + ' = "' + Output + '"';
                  2: HintStr := HintStr + ' = ' + Output;
                else
                  HintStr := HintStr + ' = Unknown error';
                end;
                P2 := Memo.RowColumnToPixels({$IFDEF UNICODE}DisplayCoord{$ELSE}Point{$ENDIF}(I, P.{$IFDEF UNICODE}Row{$ELSE}Y{$ENDIF}));
                CursorRect := Bounds(P2.X, P2.Y, (J-I) * Memo.CharWidth,
                  Memo.LineHeight);
                HideTimeout := High(Integer);  { infinite }
                Break;
              end;
              I := J;
            end;
          end
          else begin
{$IFNDEF UNICODE}
            if S[I] in ConstLeadBytes^ then
              Inc(I);
{$ENDIF}
            Inc(I);
          end;
        end;
      end;
    end;
  end
  else
    Memo.WndProc(Message);
end;

procedure TCompileForm.MemoDropFiles(Sender: TObject; X, Y: Integer;
  AFiles: TStrings);
begin
  if (AFiles.Count > 0) and ConfirmCloseFile(True) then
    OpenFile(AFiles[0], True);
end;

procedure TCompileForm.StatusBarResize(Sender: TObject);
begin
  { Without this, on Windows XP with themes, the status bar's size grip gets
    corrupted as the form is resized }
  if StatusBar.HandleAllocated then
    InvalidateRect(StatusBar.Handle, nil, True);
end;

procedure TCompileForm.WMDebuggerQueryVersion(var Message: TMessage);
begin
  Message.Result := FCompilerVersion.BinVersion;
end;

procedure TCompileForm.WMDebuggerHello(var Message: TMessage);
var
  PID: DWORD;
  WantCodeText: Boolean;
begin
  FDebugClientWnd := HWND(Message.WParam);

  { Save debug client process handle }
  if FDebugClientProcessHandle <> 0 then begin
    { Shouldn't get here, but just in case, don't leak a handle }
    CloseHandle(FDebugClientProcessHandle);
    FDebugClientProcessHandle := 0;
  end;
  PID := 0;
  if GetWindowThreadProcessId(FDebugClientWnd, @PID) <> 0 then
    FDebugClientProcessHandle := OpenProcess(SYNCHRONIZE or PROCESS_TERMINATE,
      False, PID);

  WantCodeText := Bool(Message.LParam);
  if WantCodeText then
    SendCopyDataMessageStr(FDebugClientWnd, Handle, CD_DebugClient_CompiledCodeTextA, FCompiledCodeText);
  SendCopyDataMessageStr(FDebugClientWnd, Handle, CD_DebugClient_CompiledCodeDebugInfoA, FCompiledCodeDebugInfo);

  UpdateRunMenu;
end;

procedure TCompileForm.WMDebuggerGoodbye(var Message: TMessage);
begin
  ReplyMessage(0);
  DebuggingStopped(True);
end;

function TCompileForm.GetLineNumberFromEntry(Kind, Index: Integer): Integer;
var
  I: Integer;
begin
  Result := 0;
  for I := 0 to FDebugEntriesCount-1 do begin
    if (FDebugEntries[I].Kind = Kind) and
       (FDebugEntries[I].Index = Index) then begin
      Result := FDebugEntries[I].LineNumber;
      Break;
    end;
  end;
end;

procedure TCompileForm.BringToForeground;
{ Brings our top window to the foreground. Called when pausing while
  debugging. }
var
  TopWindow: HWND;
begin
  TopWindow := GetThreadTopWindow;
  if TopWindow <> 0 then begin
    { First ask the debug client to call SetForegroundWindow() on our window.
      If we don't do this then Windows (98/2000+) will prevent our window from
      becoming activated if the debug client is currently in the foreground. }
    SendMessage(FDebugClientWnd, WM_DebugClient_SetForegroundWindow,
      WPARAM(TopWindow), 0);
    { Now call SetForegroundWindow() ourself. Why? When a remote thread calls
      SetForegroundWindow(), the request is queued; the window doesn't actually
      become active until the next time the window's thread checks the message
      queue. This call causes the window to become active immediately. }
    SetForegroundWindow(TopWindow);
  end;
end;

procedure TCompileForm.DebuggerStepped(var Message: TMessage; const Intermediate: Boolean);
var
  LineNumber: Integer;
begin
  LineNumber := GetLineNumberFromEntry(Message.WParam, Message.LParam);
  if LineNumber = 0 then
    Exit;

  if (LineNumber <= FLineStateCount) and
     (FLineState[LineNumber] <> lnEntryProcessed) then begin
    FLineState[LineNumber] := lnEntryProcessed;
    Memo.InvalidateGutterLines(LineNumber, LineNumber);
  end;

  if (FStepMode = smStepInto) or
     ((FStepMode = smStepOver) and not Intermediate) or
     ((FStepMode = smRunToCursor) and
      (FRunToCursorPoint.Kind = Message.WParam) and
      (FRunToCursorPoint.Index = Message.LParam)) or
     (FBreakPoints.IndexOf(Pointer(LineNumber)) <> -1) then begin
    MoveCaret(LineNumber, True);
    HideError;
    SetStepLine(LineNumber);
    BringToForeground;
    { Tell Setup to pause }
    Message.Result := 1;
    FPaused := True;
    UpdateRunMenu;
    UpdateCaption;
  end;
end;

procedure TCompileForm.WMDebuggerStepped(var Message: TMessage);
begin
  DebuggerStepped(Message, False);
end;

procedure TCompileForm.WMDebuggerSteppedIntermediate(var Message: TMessage);
begin
  DebuggerStepped(Message, True);
end;

procedure TCompileForm.WMDebuggerException(var Message: TMessage);
var
  LineNumber: Integer;
begin
  if FOptions.PauseOnDebuggerExceptions then begin
    LineNumber := GetLineNumberFromEntry(Message.WParam, Message.LParam);

    if (LineNumber > 0) then begin
      MoveCaret(LineNumber, True);
      SetStepLine(0);
      SetErrorLine(LineNumber);
    end;

    BringToForeground;
    { Tell Setup to pause }
    Message.Result := 1;
    FPaused := True;
    UpdateRunMenu;
    UpdateCaption;

    ReplyMessage(Message.Result);  { so that Setup enters a paused state now }
    if LineNumber > 0 then
      MsgBox(Format('Line %d:' + SNewLine + '%s.', [LineNumber, FDebuggerException]), 'Runtime Error', mbCriticalError, mb_Ok)
    else
      MsgBox(FDebuggerException + '.', 'Runtime Error', mbCriticalError, mb_Ok);
  end;
end;

procedure TCompileForm.WMDebuggerSetForegroundWindow(var Message: TMessage);
begin
  SetForegroundWindow(HWND(Message.WParam));
end;

procedure TCompileForm.WMCopyData(var Message: TWMCopyData);
var
  S: String;
begin
  case Message.CopyDataStruct.dwData of
    CD_Debugger_ReplyW: begin
        FReplyString := '';
        SetString(FReplyString, PChar(Message.CopyDataStruct.lpData),
          Message.CopyDataStruct.cbData div SizeOf(Char));
        Message.Result := 1;
      end;
    CD_Debugger_ExceptionW: begin
        SetString(FDebuggerException, PChar(Message.CopyDataStruct.lpData),
          Message.CopyDataStruct.cbData div SizeOf(Char));
        Message.Result := 1;
      end;
    CD_Debugger_UninstExeW: begin
        SetString(FUninstExe, PChar(Message.CopyDataStruct.lpData),
          Message.CopyDataStruct.cbData div sizeOf(Char));
        Message.Result := 1;
      end;
    CD_Debugger_LogMessageW: begin
        SetString(S, PChar(Message.CopyDataStruct.lpData),
          Message.CopyDataStruct.cbData div SizeOf(Char));
        DebugLogMessage(S);
        Message.Result := 1;
      end;
    CD_Debugger_TempDirW: begin
        { Paranoia: Store it in a local variable first. That way, if there's
          a problem reading the string FTempDir will be left unmodified.
          Gotta be extra careful when storing a path we'll be deleting. }
        SetString(S, PChar(Message.CopyDataStruct.lpData),
          Message.CopyDataStruct.cbData div SizeOf(Char));
        { Extreme paranoia: If there are any embedded nulls, discard it. }
        if Pos(#0, S) <> 0 then
          S := '';
        FTempDir := S;
        Message.Result := 1;
      end;
  end;
end;

procedure TCompileForm.DestroyDebugInfo;
begin
  FLineStateCapacity := 0;
  FLineStateCount := 0;
  FreeMem(FLineState);
  FLineState := nil;

  FDebugEntriesCount := 0;
  FreeMem(FDebugEntries);
  FDebugEntries := nil;

  FVariableDebugEntriesCount := 0;
  FreeMem(FVariableDebugEntries);
  FVariableDebugEntries := nil;

  FCompiledCodeText := '';
  FCompiledCodeDebugInfo := '';
end;

procedure TCompileForm.ParseDebugInfo(DebugInfo: Pointer);
{ This creates and fills the DebugEntries and FLineState arrays }
var
  Header: PDebugInfoHeader;
  Size: Cardinal;
  I: Integer;
begin
  DestroyDebugInfo;

  Header := DebugInfo;
  if (Header.ID <> DebugInfoHeaderID) or
     (Header.Version <> DebugInfoHeaderVersion) then
    raise Exception.Create('Unrecognized debug info format');

  try
    I := Memo.Lines.Count;
    FLineState := AllocMem(SizeOf(TLineState) * (I + LineStateGrowAmount));
    FLineStateCapacity := I + LineStateGrowAmount;
    FLineStateCount := I;

    Inc(Cardinal(DebugInfo), SizeOf(Header^));

    FDebugEntriesCount := Header.DebugEntryCount;
    Size := FDebugEntriesCount * SizeOf(TDebugEntry);
    GetMem(FDebugEntries, Size);
    Move(DebugInfo^, FDebugEntries^, Size);
    Inc(Cardinal(DebugInfo), Size);

    FVariableDebugEntriesCount := Header.VariableDebugEntryCount;
    Size := FVariableDebugEntriesCount * SizeOf(TVariableDebugEntry);
    GetMem(FVariableDebugEntries, Size);
    Move(DebugInfo^, FVariableDebugEntries^, Size);
    Inc(Cardinal(DebugInfo), Size);

    SetString(FCompiledCodeText, PAnsiChar(DebugInfo), Header.CompiledCodeTextLength);
    Inc(Cardinal(DebugInfo), Header.CompiledCodeTextLength);

    SetString(FCompiledCodeDebugInfo, PAnsiChar(DebugInfo), Header.CompiledCodeDebugInfoLength);

    for I := 0 to FDebugEntriesCount-1 do begin
      if (FDebugEntries[I].LineNumber > 0) and
         (FDebugEntries[I].LineNumber <= FLineStateCount) then begin
        if FLineState[FDebugEntries[I].LineNumber] = lnUnknown then
          FLineState[FDebugEntries[I].LineNumber] := lnHasEntry;
      end;
    end;
  except
    DestroyDebugInfo;
    raise;
  end;
end;

procedure TCompileForm.ResetLineState;
{ Changes green dots back to grey dots }
var
  Changed: Boolean;
  I: Integer;
begin
  Changed := False;
  for I := 1 to FLineStateCount do
    if FLineState[I] = lnEntryProcessed then begin
      FLineState[I] := lnHasEntry;
      Changed := True;
    end;
  if Changed then
    Memo.InvalidateGutter;
end;

procedure TCompileForm.CheckIfTerminated;
var
  H: THandle;
begin
  if FDebugging then begin
    { Check if the process hosting the debug client (e.g. Setup or the
      uninstaller second phase) has terminated. If the debug client hasn't
      connected yet, check the initial process (e.g. SetupLdr or the
      uninstaller first phase) instead. }
    if FDebugClientWnd <> 0 then
      H := FDebugClientProcessHandle
    else
      H := FProcessHandle;
    if WaitForSingleObject(H, 0) <> WAIT_TIMEOUT then
      DebuggingStopped(True);
  end;
end;

procedure TCompileForm.DebuggingStopped(const WaitForTermination: Boolean);

  function GetExitCodeText: String;
  var
    ExitCode: DWORD;
  begin
    { Note: When debugging an uninstall, this will get the exit code off of
      the first phase process, since that's the exit code users will see when
      running the uninstaller outside the debugger. }
    case WaitForSingleObject(FProcessHandle, 0) of
      WAIT_OBJECT_0:
        begin
          if GetExitCodeProcess(FProcessHandle, ExitCode) then begin
            { If the high bit is set, the process was killed uncleanly (e.g.
              by a debugger). Show the exit code as hex in that case. }
            if ExitCode and $80000000 <> 0 then
              Result := Format(DebugTargetStrings[FDebugTarget] + ' exit code: 0x%.8x', [ExitCode])
            else
              Result := Format(DebugTargetStrings[FDebugTarget] + ' exit code: %u', [ExitCode]);
          end
          else
            Result := 'Unable to get ' + DebugTargetStrings[FDebugTarget] + ' exit code (GetExitCodeProcess failed)';
        end;
      WAIT_TIMEOUT:
        Result := DebugTargetStrings[FDebugTarget] + ' is still running; can''t get exit code';
    else
      Result := 'Unable to get ' + DebugTargetStrings[FDebugTarget] +  ' exit code (WaitForSingleObject failed)';
    end;
  end;

var
  ExitCodeText: String;
begin
  if WaitForTermination then begin
    { Give the initial process time to fully terminate so we can successfully
      get its exit code }  
    WaitForSingleObject(FProcessHandle, 5000);
  end;
  FDebugging := False;
  FDebugClientWnd := 0;
  ExitCodeText := GetExitCodeText;
  if FDebugClientProcessHandle <> 0 then begin
    CloseHandle(FDebugClientProcessHandle);
    FDebugClientProcessHandle := 0;
  end;
  CloseHandle(FProcessHandle);
  FProcessHandle := 0;
  FTempDir := '';
  CheckIfRunningTimer.Enabled := False;
  HideError;
  SetStepLine(0);
  UpdateRunMenu;
  UpdateCaption;
  DebugLogMessage('*** ' + ExitCodeText);
  StatusBar.Panels[spExtraStatus].Text := ' ' + ExitCodeText;
end;

procedure TCompileForm.DetachDebugger;
begin
  CheckIfTerminated;
  if not FDebugging then Exit;
  SendNotifyMessage(FDebugClientWnd, WM_DebugClient_Detach, 0, 0);
  DebuggingStopped(False);
end;

function TCompileForm.AskToDetachDebugger: Boolean;
begin
  if FDebugClientWnd = 0 then begin
    MsgBox('Please stop the running ' + DebugTargetStrings[FDebugTarget] +  ' process before performing this command.',
      SCompilerFormCaption, mbError, MB_OK);
    Result := False;
  end else if MsgBox('This command will detach the debugger from the running ' + DebugTargetStrings[FDebugTarget] + ' process. Continue?',
     SCompilerFormCaption, mbError, MB_OKCANCEL) = IDOK then begin
    DetachDebugger;
    Result := True;
  end else
    Result := False;
end;

procedure TCompileForm.UpdateRunMenu;
begin
  CheckIfTerminated;
  BCompile.Enabled := not FCompiling and not FDebugging;
  CompileButton.Enabled := BCompile.Enabled;
  BStopCompile.Enabled := FCompiling;
  StopCompileButton.Enabled := BStopCompile.Enabled;
  RRun.Enabled := not FCompiling and (not FDebugging or FPaused);
  RunButton.Enabled := RRun.Enabled;
  RPause.Enabled := FDebugging and not FPaused;
  PauseButton.Enabled := RPause.Enabled;
  RRunToCursor.Enabled := RRun.Enabled;
  RStepInto.Enabled := RRun.Enabled;
  RStepOver.Enabled := RRun.Enabled;
  RTerminate.Enabled := FDebugging and (FDebugClientWnd <> 0);
  REvaluate.Enabled := FDebugging and (FDebugClientWnd <> 0);
end;

procedure TCompileForm.UpdateTargetMenu;
begin
  if FDebugTarget = dtSetup then begin
    RTargetSetup.Checked := True;
    TargetSetupButton.Down := True;
  end else begin
    RTargetUninstall.Checked := True;
    TargetUninstallButton.Down := True;
  end;
end;

procedure TCompileForm.UpdateThemeData(const Close, Open: Boolean);
begin
  if Close then begin
    if FProgressThemeData <> 0 then begin
      CloseThemeData(FProgressThemeData);
      FProgressThemeData := 0;
    end;
  end;

  if Open then begin
    if UseThemes then begin
      FProgressThemeData := OpenThemeData(Handle, 'Progress');
      if (GetThemeInt(FProgressThemeData, 0, 0, TMT_PROGRESSCHUNKSIZE, FProgressChunkSize) <> S_OK) or
         (FProgressChunkSize <= 0) then
        FProgressChunkSize := 6;
      if (GetThemeInt(FProgressThemeData, 0, 0, TMT_PROGRESSSPACESIZE, FProgressSpaceSize) <> S_OK) or
         (FProgressSpaceSize < 0) then  { ...since "OpusOS" theme returns a bogus -1 value }
        FProgressSpaceSize := 2;
    end else
      FProgressThemeData := 0;
  end;
end;

procedure TCompileForm.StartProcess;
var
  RunFilename, RunParameters, WorkingDir: String;
  Info: TShellExecuteInfo;
  SaveFocusWindow: HWND;
  WindowList: Pointer;
  ShellExecuteResult: BOOL;
  ErrorCode: DWORD;
begin
  if FDebugTarget = dtUninstall then begin
    if FUninstExe = '' then
      raise Exception.Create(SCompilerNeedUninstExe);
    RunFilename := FUninstExe;
  end else
    RunFilename := FCompiledExe;
  RunParameters := Format('/DEBUGWND=$%x ', [Handle]) + FRunParameters;

  ResetLineState;
  DebugOutputList.Clear;
  SendMessage(DebugOutputList.Handle, LB_SETHORIZONTALEXTENT, 0, 0);
  TabSet.TabIndex := tiDebugOutput;
  SetStatusPanelVisible(True);

  FillChar(Info, SizeOf(Info), 0);
  Info.cbSize := SizeOf(Info);
  Info.fMask := SEE_MASK_FLAG_NO_UI or SEE_MASK_FLAG_DDEWAIT or
    SEE_MASK_NOCLOSEPROCESS;
  Info.Wnd := Application.Handle;
  if FOptions.RunAsDifferentUser and (Win32MajorVersion >= 5) then
    Info.lpVerb := 'runas'
  else
    Info.lpVerb := 'open';
  Info.lpFile := PChar(RunFilename);
  Info.lpParameters := PChar(RunParameters);
  WorkingDir := PathExtractDir(RunFilename);
  Info.lpDirectory := PChar(WorkingDir);
  Info.nShow := SW_SHOWNORMAL;
  { Disable windows so that the user can't click other things while a "Run as"
    dialog is up on Windows 2000/XP (they aren't system modal like on Vista) }
  SaveFocusWindow := GetFocus;
  WindowList := DisableTaskWindows(0);
  try
    { Also temporarily remove the focus since a disabled window's children can
      still receive keystrokes. This is needed on Vista if the UAC dialog
      doesn't come to the foreground for some reason (e.g. if the following
      SetActiveWindow call is removed). }
    Windows.SetFocus(0);
    { On Vista, when disabling windows, we have to make the application window
      the active window, otherwise the UAC dialog doesn't come to the
      foreground automatically. Note: This isn't done on older versions simply
      to avoid unnecessary title bar flicker. }
    if Win32MajorVersion >= 6 then
      SetActiveWindow(Application.Handle);
    ShellExecuteResult := ShellExecuteEx(@Info);
    ErrorCode := GetLastError;
  finally
    EnableTaskWindows(WindowList);
    Windows.SetFocus(SaveFocusWindow);
  end;
  if not ShellExecuteResult then begin
    { Don't display error message if user clicked Cancel at UAC dialog }
    if ErrorCode = ERROR_CANCELLED then
      Abort;
    raise Exception.CreateFmt(SCompilerExecuteSetupError2, [RunFilename,
      ErrorCode, Win32ErrorString(ErrorCode)]);
  end;
  FDebugging := True;
  FPaused := False;
  FProcessHandle := Info.hProcess;
  CheckIfRunningTimer.Enabled := True;
  UpdateRunMenu;
  UpdateCaption;
  DebugLogMessage('*** ' + DebugTargetStrings[FDebugTarget] + ' started');
end;

procedure TCompileForm.CompileIfNecessary;
begin
  CheckIfTerminated;

  { Display warning if the user modified the script while running }
  if FDebugging and FModifiedSinceLastCompileAndGo then begin
    if MsgBox('The changes you made will not take effect until you ' +
       're-compile.' + SNewLine2 + 'Continue running anyway?',
       SCompilerFormCaption, mbError, MB_YESNO) <> IDYES then
      Abort;
    FModifiedSinceLastCompileAndGo := False;
    { The process may have terminated while the message box was up; check,
      and if it has, we want to recompile below }
    CheckIfTerminated;
  end;

  if not FDebugging and FModifiedSinceLastCompile then
    CompileFile('', False);
end;

procedure TCompileForm.Go(AStepMode: TStepMode);
begin
  CompileIfNecessary;
  FStepMode := AStepMode;
  HideError;
  SetStepLine(0);
  if FDebugging then begin
    if FPaused then begin
      FPaused := False;
      UpdateRunMenu;
      UpdateCaption;
      { Tell it to continue }
      SendNotifyMessage(FDebugClientWnd, WM_DebugClient_Continue,
        Ord(AStepMode = smStepOver), 0);
    end;
  end
  else
    StartProcess;
end;

function TCompileForm.EvaluateConstant(const S: String;
  var Output: String): Integer;
begin
  FReplyString := '';
  Result := SendCopyDataMessageStr(FDebugClientWnd, Handle,
    CD_DebugClient_EvaluateConstantW, S);
  if Result > 0 then
    Output := FReplyString;
end;

function TCompileForm.EvaluateVariableEntry(const DebugEntry: PVariableDebugEntry;
  var Output: String): Integer;
begin
  FReplyString := '';
  Result := SendCopyDataMessage(FDebugClientWnd, Handle, CD_DebugClient_EvaluateVariableEntry,
    DebugEntry, SizeOf(DebugEntry^));
  if Result > 0 then
    Output := FReplyString;
end;

procedure TCompileForm.RRunClick(Sender: TObject);
begin
  Go(smRun);
end;

procedure TCompileForm.RParametersClick(Sender: TObject);
begin
  InputQuery('Run Parameters', 'Command line parameters for ' + DebugTargetStrings[dtSetup] +
    ' and ' + DebugTargetStrings[dtUninstall] + ':', FRunParameters);
end;

procedure TCompileForm.RPauseClick(Sender: TObject);
begin
  if FDebugging and not FPaused then begin
    if FStepMode <> smStepInto then begin
      FStepMode := smStepInto;
      UpdateCaption;
    end
    else
      MsgBox('A pause is already pending.', SCompilerFormCaption, mbError,
        MB_OK);
  end;
end;

procedure TCompileForm.RRunToCursorClick(Sender: TObject);

  function GetDebugEntryFromLineNumber(LineNumber: Integer;
    var DebugEntry: TDebugEntry): Boolean;
  var
    I: Integer;
  begin
    Result := False;
    for I := 0 to FDebugEntriesCount-1 do begin
      if FDebugEntries[I].LineNumber = LineNumber then begin
        DebugEntry := FDebugEntries[I];
        Result := True;
        Break;
      end;
    end;
  end;

begin
  CompileIfNecessary;
  if not GetDebugEntryFromLineNumber(Memo.CaretY, FRunToCursorPoint) then begin
    MsgBox('No code was generated for the current line.', SCompilerFormCaption,
      mbError, MB_OK);
    Exit;
  end;
  Go(smRunToCursor);
end;

procedure TCompileForm.RStepIntoClick(Sender: TObject);
begin
  Go(smStepInto);
end;

procedure TCompileForm.RStepOverClick(Sender: TObject);
begin
  Go(smStepOver);
end;

procedure TCompileForm.RTerminateClick(Sender: TObject);
var
  S, Dir: String;
begin
  S := 'This will unconditionally terminate the running ' +
       DebugTargetStrings[FDebugTarget] + ' process. Continue?';

  if FDebugTarget = dtSetup then
    S := S + #13#10#13#10'Note that if ' + DebugTargetStrings[FDebugTarget] + ' ' +
         'is currently in the installation phase, any changes made to the ' +
         'system thus far will not be undone, nor will uninstall data be written.';

  if MsgBox(S, 'Terminate', mbConfirmation, MB_YESNO or MB_DEFBUTTON2) <> IDYES then
    Exit;
  CheckIfTerminated;
  if FDebugging then begin
    DebugLogMessage('*** Terminating process');
    Win32Check(TerminateProcess(FDebugClientProcessHandle, 6));
    if (WaitForSingleObject(FDebugClientProcessHandle, 5000) <> WAIT_TIMEOUT) and
       (FTempDir <> '') then begin
      Dir := FTempDir;
      FTempDir := '';
      DebugLogMessage('*** Removing left-over temporary directory: ' + Dir);
      { Sleep for a bit to allow files to be unlocked by Windows,
        otherwise it fails intermittently (with Hyper-Threading, at least) }
      Sleep(50);
      if not DeleteDirTree(Dir) and DirExists(Dir) then
        DebugLogMessage('*** Failed to remove temporary directory');
    end;
    DebuggingStopped(True);
  end;
end;

procedure TCompileForm.REvaluateClick(Sender: TObject);
var
  Output: String;
begin
  if InputQuery('Evaluate', 'Constant to evaluate (e.g., "{app}"):',
     FLastEvaluateConstantText) then begin
    case EvaluateConstant(FLastEvaluateConstantText, Output) of
      1: MsgBox(Output, 'Evaluate Result', mbInformation, MB_OK);
      2: MsgBox(Output, 'Evaluate Error', mbError, MB_OK);
    else
      MsgBox('An unknown error occurred.', 'Evaluate Error', mbError, MB_OK);
    end;
  end;
end;

procedure TCompileForm.CheckIfRunningTimerTimer(Sender: TObject);
begin
  { In cases of normal Setup termination, we receive a WM_Debugger_Goodbye
    message. But in case we don't get that, use a timer to periodically check
    if the process is no longer running. }
  CheckIfTerminated;
end;

procedure TCompileForm.PListCopyClick(Sender: TObject);
begin
  Clipboard.AsText := (ListPopupMenu.PopupComponent as TListBox).Items.Text;
end;

procedure TCompileForm.AppOnIdle(Sender: TObject; var Done: Boolean);
begin
  { For an explanation of this, see the comment where HandleMessage is called }
  if FCompiling then
    Done := False;

  FBecameIdle := True;
end;

procedure TCompileForm.EGotoClick(Sender: TObject);
var
  S: String;
  L: Integer;
begin
  S := IntToStr(Memo.CaretY);
  if InputQuery('Go to Line', 'Line number:', S) then begin
    L := StrToIntDef(S, Low(L));
    if L <> Low(L) then
      Memo.CaretY := L;
  end;
end;

procedure TCompileForm.StatusBarDrawPanel(StatusBar: TStatusBar;
  Panel: TStatusPanel; const Rect: TRect);
var
  R, BR: TRect;
  W, ChunkCount: Integer;
begin
  case Panel.Index of
    spCompileIcon:
      if FCompiling then begin
        ImageList_Draw(FBuildImageList, FBuildAnimationFrame, StatusBar.Canvas.Handle,
          Rect.Left + ((Rect.Right - Rect.Left) - 17) div 2,
          Rect.Top + ((Rect.Bottom - Rect.Top) - 15) div 2, ILD_NORMAL);
      end;
    spCompileProgress:
      if FCompiling and (FProgressMax > 0) then begin
        R := Rect;
        InflateRect(R, -2, -2);
        if FProgressThemeData = 0 then begin
          R.Right := R.Left + MulDiv(FProgress, R.Right - R.Left,
            FProgressMax);
          StatusBar.Canvas.Brush.Color := clHighlight;
          StatusBar.Canvas.FillRect(R);
        end else begin
          DrawThemeBackground(FProgressThemeData, StatusBar.Canvas.Handle, PP_BAR, 0, R, nil);
          BR := R;
          GetThemeBackgroundContentRect(FProgressThemeData, StatusBar.Canvas.Handle, PP_BAR, 0, BR, @R);
          IntersectClipRect(StatusBar.Canvas.Handle, R.Left, R.Top, R.Right, R.Bottom);
          W := MulDiv(FProgress, R.Right - R.Left, FProgressMax);
          ChunkCount := W div (FProgressChunkSize + FProgressSpaceSize);
          if W mod (FProgressChunkSize + FProgressSpaceSize) > 0 then
            Inc(ChunkCount);
          R.Right := R.Left + FProgressChunkSize;
          for W := 0 to ChunkCount - 1 do
          begin
            DrawThemeBackground(FProgressThemeData, StatusBar.Canvas.Handle, PP_CHUNK, 0, R, nil);
            OffsetRect(R, FProgressChunkSize + FProgressSpaceSize, 0);
          end;
        end;
      end;
  end;
end;

procedure TCompileForm.InvalidateStatusPanel(const Index: Integer);
var
  R: TRect;
begin
  { For some reason, the VCL doesn't offer a method for this... }
  if SendMessage(StatusBar.Handle, SB_GETRECT, Index, LPARAM(@R)) <> 0 then begin
    InflateRect(R, -1, -1);
    InvalidateRect(StatusBar.Handle, @R, True);
  end;
end;

procedure TCompileForm.UpdateCompileStatusPanels(const AProgress,
  AProgressMax: Cardinal; const ASecondsRemaining: Integer;
  const ABytesCompressedPerSecond: Cardinal);
var
  T: DWORD;
  Progress64, Progressmax64: dwInteger64;
begin
  { Icon panel }
  T := GetTickCount;
  if Cardinal(T - FLastAnimationTick) >= Cardinal(500) then begin
    FLastAnimationTick := T;
    InvalidateStatusPanel(spCompileIcon);
    FBuildAnimationFrame := (FBuildAnimationFrame + 1) mod 4;
    { Also update the status text twice a second }
    if ASecondsRemaining >= 0 then
      StatusBar.Panels[spExtraStatus].Text := Format(
        ' Estimated time remaining: %.2d%s%.2d%s%.2d     Average KB/sec: %.0n',
        [(ASecondsRemaining div 60) div 60, TimeSeparator,
         (ASecondsRemaining div 60) mod 60, TimeSeparator,
         ASecondsRemaining mod 60, ABytesCompressedPerSecond / 1024])
    else
      StatusBar.Panels[spExtraStatus].Text := '';
  end;

  { Progress panel and taskbar progress bar }
  if (FProgress <> AProgress) or
     (FProgressMax <> AProgressMax) then begin
    FProgress := AProgress;
    FProgressMax := AProgressMax;
    InvalidateStatusPanel(spCompileProgress);
    if FTaskbarList3 <> nil then begin
      Progress64.Lo := FProgress;
      Progress64.Hi := 0;
      ProgressMax64.Lo := FProgressMax;
      ProgressMax64.Hi := 0;
      FTaskbarList3.SetProgressValue(Application.Handle, Progress64, ProgressMax64);
    end;
  end;
end;

procedure TCompileForm.WMThemeChanged(var Message: TMessage);
begin
  { Don't Run to Cursor into this function, it will interrupt up the theme change }
  UpdateThemeData(True, True);
  inherited;
end;

procedure TCompileForm.RTargetClick(Sender: TObject);
var
  NewTarget: TDebugTarget;
begin
  if (Sender = RTargetSetup) or (Sender = TargetSetupButton) then
    NewTarget := dtSetup
  else
    NewTarget := dtUninstall;
  if (FDebugTarget <> NewTarget) and (not FDebugging or AskToDetachDebugger) then
    FDebugTarget := NewTarget;

  { Update always even if the user decided not to switch so the states are restored }
  UpdateTargetMenu;
end;

procedure TCompileForm.AppOnActivate(Sender: TObject);
const
  ReloadMessages: array[Boolean] of String = (
    'The file has been modified outside of the source editor.' + SNewLine2 +
      'Do you want to reload the file?',
    'The file has been modified outside of the source editor. Changes have ' +
      'also been made in the source editor.' + SNewLine2 + 'Do you want to ' +
      'reload the file and lose the changes made in the source editor?');
var
  NewTime: TFileTime;
  Changed: Boolean;
begin
  if FFilename = '' then
    Exit;

  { See if the file has been modified outside the editor }
  Changed := False;
  if GetLastWriteTimeOfFile(FFilename, NewTime) then begin
    if CompareFileTime(FFileLastWriteTime, NewTime) <> 0 then begin
      FFileLastWriteTime := NewTime;
      Changed := True;
    end;
  end;

  { If it has been, offer to reload it }
  if Changed then begin
    if IsWindowEnabled(Application.Handle) then begin
      if MsgBox(FFilename + SNewLine2 + ReloadMessages[Memo.Modified],
         SCompilerFormCaption, mbConfirmation, MB_YESNO) = IDYES then
        if ConfirmCloseFile(False) then
          OpenFile(FFilename, False);
    end
    else begin
      { When a modal dialog is up, don't offer to reload the file. Probably
        not a good idea since the dialog might be manipulating the file. }
      MsgBox(FFilename + SNewLine2 + 'The file has been modified outside ' +
        'of the source editor. You might want to reload it.',
        SCompilerFormCaption, mbInformation, MB_OK);
    end;
  end;
end;

procedure TCompileForm.DebugOutputListDrawItem(Control: TWinControl;
  Index: Integer; Rect: TRect; State: TOwnerDrawState);

  function SafeGetItem(const ListBoxHandle: HWND; const Index: Integer): String;
  { Prior to Delphi 6, the VCL will incur a buffer overflow if you trying
    reading an item from a TListBox longer than 4096 characters. }
  var
    Len: Integer;
  begin
    Len := SendMessage(ListBoxHandle, LB_GETTEXTLEN, Index, 0);
    if Len <= 0 then
      Result := ''  { zero length or out of range? }
    else begin
      SetString(Result, nil, Len);
      Len := SendMessage(ListBoxHandle, LB_GETTEXT, Index, LPARAM(Result));
      if Len <= 0 then
        Result := ''  { failed? }
      else
        SetLength(Result, Len);  { since LB_GETTEXTLEN can overestimate }
    end;
  end;

var
  S: String;
begin
  { An owner drawn list box is used for precise tab expansion }  
  S := SafeGetItem(DebugOutputList.Handle, Index);
  DebugOutputList.Canvas.FillRect(Rect);
  Inc(Rect.Left, 2);
  if (S <> '') and (S[1] = #9) then
    DebugOutputList.Canvas.TextOut(Rect.Left + FDebugLogListTimeWidth,
      Rect.Top, Copy(S, 2, Maxint))
  else begin
    if (Length(S) > 16) and (S[14] = '-') and (S[15] = '-') and (S[16] = ' ') then begin
      { Draw lines that begin with '-- ' (like '-- File entry --') in bold }
      DebugOutputList.Canvas.TextOut(Rect.Left, Rect.Top, Copy(S, 1, 13));
      DebugOutputList.Canvas.Font.Style := [fsBold];
      DebugOutputList.Canvas.TextOut(Rect.Left + FDebugLogListTimeWidth,
        Rect.Top, Copy(S, 14, Maxint));
    end
    else
      DebugOutputList.Canvas.TextOut(Rect.Left, Rect.Top, S);
  end;
end;

procedure TCompileForm.TabSetClick(Sender: TObject);
begin
  case TabSet.TabIndex of
    tiCompilerOutput:
      begin
        CompilerOutputList.BringToFront;
        CompilerOutputList.Visible := True;
        DebugOutputList.Visible := False;
      end;
    tiDebugOutput:
      begin
        DebugOutputList.BringToFront;
        DebugOutputList.Visible := True;
        CompilerOutputList.Visible := False;
      end;
  end;
end;

procedure TCompileForm.ToggleBreakPoint(Line: Integer);
var
  I: Integer;
begin
  I := FBreakPoints.IndexOf(Pointer(Line));
  if I = -1 then
    FBreakPoints.Add(Pointer(Line))
  else
    FBreakPoints.Delete(I);
  Memo.InvalidateGutterLines(Line, Line);
  Memo.InvalidateLine(Line);
end;

{$IFDEF UNICODE}
procedure TCompileForm.MemoGutterClick(Sender: TObject; Button: TMouseButton;X, Y,
  Line: Integer; mark: TSynEditMark);
{$ELSE}
procedure TCompileForm.MemoGutterClick(Sender: TObject; X, Y,
  Line: Integer; mark: TSynEditMark);
{$ENDIF}
begin
  { Work around possible SynEdit bug: Ignore clicks on the gutter when the
    cursor is an I-beam, so that if you're selecting text with the mouse and
    inadvertently release the mouse over the gutter it won't create a
    breakpoint. Like Delphi's editor. }
  if GetCursor <> LoadCursor(0, IDC_IBEAM) then
    ToggleBreakPoint(Line);
end;

procedure TCompileForm.MemoLinesInserted(FirstLine, Count: integer);
var
  I, Line: Integer;
begin
  { Sanity checks -- I don't trust SynEdit }
  if (Count <= 0) or (FirstLine <= 0) then
    Exit;

  for I := 0 to FDebugEntriesCount-1 do
    if FDebugEntries[I].LineNumber >= FirstLine then
      Inc(FDebugEntries[I].LineNumber, Count);

  if Assigned(FLineState) and (FirstLine <= FLineStateCount) then begin
    { Grow FStateLine if necessary }
    I := (FLineStateCount + Count) - FLineStateCapacity;
    if I > 0 then begin
      if I < LineStateGrowAmount then
        I := LineStateGrowAmount;
      ReallocMem(FLineState, SizeOf(TLineState) * (FLineStateCapacity + I));
      Inc(FLineStateCapacity, I);
    end;
    { Shift existing line states and clear the new ones }
    for I := FLineStateCount downto FirstLine do
      FLineState[I + Count] := FLineState[I];
    for I := FirstLine to FirstLine + Count - 1 do
      FLineState[I] := lnUnknown;
    Inc(FLineStateCount, Count);
  end;

  if FStepLine >= FirstLine then
    Inc(FStepLine, Count);
  if FErrorLine >= FirstLine then
    Inc(FErrorLine, Count);

  for I := 0 to FBreakPoints.Count-1 do begin
    Line := Integer(FBreakPoints[I]);
    if Line >= FirstLine then
      FBreakPoints[I] := Pointer(Line + Count);
  end;
end;

procedure TCompileForm.MemoLinesDeleted(FirstLine, Count: integer);
var
  I, Line: Integer;
  DebugEntry: PDebugEntry;
  Pack: Boolean;
begin
  { When deleting a highlighted block in the middle of a line, we get called
    with Count=0 (a probable SynEdit bug) }
  if (Count <= 0) or (FirstLine <= 0) then
    Exit;

  for I := 0 to FDebugEntriesCount-1 do begin
    DebugEntry := @FDebugEntries[I];
    if DebugEntry.LineNumber >= FirstLine then begin
      if DebugEntry.LineNumber < FirstLine + Count then
        DebugEntry.LineNumber := 0
      else
        Dec(DebugEntry.LineNumber, Count);
    end;
  end;

  if Assigned(FLineState) then begin
    { Shift existing line states }
    if FirstLine <= FLineStateCount - Count then begin
      for I := FirstLine to FLineStateCount - Count do
        FLineState[I] := FLineState[I + Count];
      Dec(FLineStateCount, Count);
    end
    else begin
      { There's nothing to shift because the last line(s) were deleted, or
        line(s) past FLineStateCount }
      if FLineStateCount >= FirstLine then
        FLineStateCount := FirstLine - 1;
    end;
  end;

  if FStepLine >= FirstLine then begin
    if FStepLine < FirstLine + Count then
      FStepLine := 0
    else
      Dec(FStepLine, Count);
  end;
  if FErrorLine >= FirstLine then begin
    if FErrorLine < FirstLine + Count then
      FErrorLine := 0
    else
      Dec(FErrorLine, Count);
  end;

  Pack := False;

  for I := 0 to FBreakPoints.Count-1 do begin
    Line := Integer(FBreakPoints[I]);
    if Line >= FirstLine then begin
      if Line < FirstLine + Count then begin
        FBreakPoints[I] := nil;
        Pack := True;
      end else begin
        Line := Line - Count;
        FBreakPoints[I] := Pointer(Line);
      end;
    end;
  end;

  if Pack then
    FBreakPoints.Pack;
end;

procedure TCompileForm.RToggleBreakPointClick(Sender: TObject);
begin
  ToggleBreakPoint(Memo.CaretY);
end;

{$IFNDEF UNICODE}
var
  Compil32LeadBytes: TLeadByteSet;
{$ENDIF}

initialization
{$IFNDEF UNICODE}
  GetLeadBytes(Compil32LeadBytes);
  ConstLeadBytes := @Compil32LeadBytes;
{$ENDIF}
  InitThemeLibrary;
  InitHtmlHelpLibrary;
  { For ClearType support, try to make the default font Microsoft Sans Serif }
  if DefFontData.Name = 'MS Sans Serif' then
    DefFontData.Name := AnsiString(GetPreferredUIFont);
  CoInitialize(nil);
finalization
  CoUninitialize();
end.
