{
 ***************************************************************************
 *                                                                         *
 *   This source is free software; you can redistribute it and/or modify   *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This code is distributed in the hope that it will be useful, but      *
 *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
 *   General Public License for more details.                              *
 *                                                                         *
 *   A copy of the GNU General Public License is available on the World    *
 *   Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also      *
 *   obtain it by writing to the Free Software Foundation,                 *
 *   Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.   *
 *                                                                         *
 ***************************************************************************

  Author: Mattias Gaertner

  Abstract:
    Standard Quick Fixes - tools to help fixing (compiler) messages.

  ToDo:
    - cant find unit: duplicate include file, e.g. control.inc
    - TQuickFixIdentifierNotFoundAddLocal: extend with add private/public
    - local var not used: remove declaration and all assignments
    - There is no method in an ancestor class to be overriden:
      1. option: if the ancestor has a function with the same name: update the parameter list
      2. option: remove the method
      3. option: add a virtual method to the ancestor
    - function header doesn't match any method: update from interface/class
    - function header doesn't match any method: update interface/class
    - complete function implementation with missing parameters
    - private variable not used => remove
    - Hint/Warning: (5036) Local variable "Path" does not seem to be initialized
         auto add begin+end
         Pointer:=nil
         integer:=0
         string:=''
         record: FillByte(p %H-,SizeOf(p),0)
         set:=[]
         enum:=low(enum);
         default()
    - Hint: function result does not seem to be initialized, see above for local var
}
unit etQuickFixes;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, AVL_Tree,
  // LCL
  Menus, Dialogs, Controls,
  // LazUtils
  LazLoggerBase, AvgLvlTree, LazFileUtils, LazStringUtils,
  // Codetools
  CodeToolManager, CodeCache, CodeTree, CodeAtom, BasicCodeTools, KeywordFuncLists,
  // IdeIntf
  IDEExternToolIntf, IDEMsgIntf, LazIDEIntf, IDEDialogs, MenuIntf,
  ProjectIntf, PackageIntf, CompOptsIntf,
  // IDE
  LazarusIDEStrConsts, etFPCMsgParser, AbstractsMethodsDlg, QFInitLocalVarDlg;

type

  { TQuickFixIdentifierNotFoundAddLocal }

  TQuickFixIdentifierNotFoundAddLocal = class(TMsgQuickFix)
  public
    function IsApplicable(Msg: TMessageLine; out Identifier: string): boolean;
    procedure CreateMenuItems(Fixes: TMsgQuickFixes); override;
    procedure QuickFix({%H-}Fixes: TMsgQuickFixes; Msg: TMessageLine); override;
  end;

  { TQuickFixLocalVariableNotUsed_Remove }

  TQuickFixLocalVariableNotUsed_Remove = class(TMsgQuickFix)
  public
    function IsApplicable(Msg: TMessageLine; out Identifier: string): boolean;
    procedure CreateMenuItems(Fixes: TMsgQuickFixes); override;
    procedure QuickFix({%H-}Fixes: TMsgQuickFixes; Msg: TMessageLine); override;
  end;

  { TQuickFixLocalVarNotInitialized_AddAssignment }

  TQuickFixLocalVarNotInitialized_AddAssignment = class(TMsgQuickFix)
  public
    function IsApplicable(Msg: TMessageLine; out Identifier: string): boolean;
    procedure CreateMenuItems(Fixes: TMsgQuickFixes); override;
    procedure QuickFix({%H-}Fixes: TMsgQuickFixes; Msg: TMessageLine); override;
  end;

  { TQuickFixUnitNotFound_Remove, also "unit not used" }

  TQuickFixUnitNotFound_Remove = class(TMsgQuickFix)
  public
    function IsApplicable(Msg: TMessageLine;
      out MissingUnitName, UsedByUnit: string): boolean;
    procedure CreateMenuItems(Fixes: TMsgQuickFixes); override;
    procedure QuickFix({%H-}Fixes: TMsgQuickFixes; Msg: TMessageLine); override;
  end;

  { TQuickFixClassWithAbstractMethods
    Quick fix for example:
    Warning: Constructing a class "TClassA" with abstract methods }

  TQuickFixClassWithAbstractMethods = class(TMsgQuickFix)
  public
    function IsApplicable(Msg: TMessageLine; out aClassName: string): boolean;
    procedure CreateMenuItems(Fixes: TMsgQuickFixes); override;
    procedure QuickFix({%H-}Fixes: TMsgQuickFixes; Msg: TMessageLine); override;
  end;

  { TQuickFixSrcPathOfPkgContains_OpenPkg
    QuickFix for IDE warning "other sources path of package %s contains directory "%s", ..."
    Open Package
    }

  TQuickFixSrcPathOfPkgContains_OpenPkg = class(TMsgQuickFix)
  public
    function IsApplicable(Msg: TMessageLine; out PkgName: string): boolean;
    procedure CreateMenuItems(Fixes: TMsgQuickFixes); override;
    procedure QuickFix({%H-}Fixes: TMsgQuickFixes; Msg: TMessageLine); override;
  end;

  { TQuickFix_HideWithIDEDirective - hide with IDE directive %H- }

  TQuickFix_HideWithIDEDirective = class(TMsgQuickFix)
  public
    function IsApplicable(Msg: TMessageLine): boolean;
    procedure CreateMenuItems(Fixes: TMsgQuickFixes); override;
    procedure QuickFix(Fixes: TMsgQuickFixes; Msg: TMessageLine); override;
  end;

  { TQuickFix_HideWithCompilerOption - hide with compiler option -vm<id> }

  TQuickFix_HideWithCompilerOption = class(TMsgQuickFix)
  public
    function IsApplicable(Msg: TMessageLine; out ToolData: TIDEExternalToolData;
      out IDETool: TObject): boolean;
    procedure CreateMenuItems(Fixes: TMsgQuickFixes); override;
    procedure QuickFix({%H-}Fixes: TMsgQuickFixes; Msg: TMessageLine); override;
  end;

  { TQuickFix_HideWithCompilerDirective - hide with compiler directive $warn <id> off }

  TQuickFix_HideWithCompilerDirective = class(TMsgQuickFix)
  public
    function IsApplicable(Msg: TMessageLine; out MsgID: integer;
      out Tool: TCodeTool): boolean;
    procedure CreateMenuItems(Fixes: TMsgQuickFixes); override;
    procedure QuickFix({%H-}Fixes: TMsgQuickFixes; Msg: TMessageLine); override;
  end;

  { TQuickFixInheritedMethodIsHidden_AddModifier - add proc modifier 'overload' or 'reintroduce' }

  TQuickFixInheritedMethodIsHidden_AddModifier = class(TMsgQuickFix)
    function IsApplicable(Msg: TMessageLine; out MsgID: integer): boolean;
    procedure CreateMenuItems(Fixes: TMsgQuickFixes); override;
    procedure QuickFix({%H-}Fixes: TMsgQuickFixes; Msg: TMessageLine); override;
  end;

  { TIDEQuickFixes }

  TIDEQuickFixes = class(TMsgQuickFixes)
  private
    FParentMenuItem: TIDEMenuSection;
    fMenuItemToInfo: TPointerToPointerTree; // TIDEMenuCommand to TMenuItemInfo
    procedure MenuItemClick(Sender: TObject);
  public
    constructor Create(aOwner: TComponent); override;
    destructor Destroy; override;
    procedure ClearLines;
    procedure SetMsgLines(aMsg: TMessageLine);
    procedure AddMsgLine(aMsg: TMessageLine);
    procedure OnPopupMenu(aParentMenuItem: TIDEMenuSection);
    function AddMenuItem(Fix: TMsgQuickFix; Msg: TMessageLine; aCaption: string;
      aTag: PtrInt=0): TIDEMenuCommand; override;
    function OpenMsg(Msg: TMessageLine): boolean;
    property ParentMenuItem: TIDEMenuSection read FParentMenuItem write FParentMenuItem;
  end;

var
  IDEQuickFixes: TIDEQuickFixes = nil;

function GetMsgCodetoolPos(Msg: TMessageLine; out Code: TCodeBuffer;
  out Tool: TCodeTool; out CleanPos: integer; out Node: TCodeTreeNode): boolean;
function GetMsgSrcPosOfIdentifier(Msg: TMessageLine; out Identifier: string;
  out Code: TCodeBuffer; out Tool: TCodeTool; out CleanPos: integer;
  out Node: TCodeTreeNode): boolean;
function GetMsgSrcPosOfThisIdentifier(Msg: TMessageLine; const Identifier: string;
  out Code: TCodeBuffer; out Tool: TCodeTool; out CleanPos: integer;
  out Node: TCodeTreeNode): boolean;

implementation

type
  TMenuItemInfo = class
  public
    MenuItem: TIDEMenuCommand;
    Fix: TMsgQuickFix;
    Msg: TMessageLine;
  end;

procedure ShowError(Msg: string);
begin
  IDEMessageDialog(lisQuickFixError, Msg, mtError, [mbCancel]);
end;

function IsIdentifierInCode(Code: TCodeBuffer; X,Y: integer;
  Identifier, ErrorMsg: string): boolean;
var
  p: integer;
  IdentStart: integer;
  IdentEnd: integer;
begin
  Result:=false;
  if Code=nil then begin
    ShowError(ErrorMsg+' (Code=nil)');
    exit;
  end;
  Code.LineColToPosition(Y,X,p);
  if p<1 then begin
    ShowError(Format(lisPositionOutsideOfSource, [ErrorMsg]));
    exit;
  end;
  GetIdentStartEndAtPosition(Code.Source,p,IdentStart,IdentEnd);
  if SysUtils.CompareText(Identifier,copy(Code.Source,IdentStart,IdentEnd-IdentStart))<>0
  then begin
    ShowError(ErrorMsg);
    exit;
  end;
  Result:=true;
end;

function GetMsgCodetoolPos(Msg: TMessageLine; out Code: TCodeBuffer;
  out Tool: TCodeTool; out CleanPos: integer; out Node: TCodeTreeNode): boolean;
var
  Filename: String;
begin
  Result:=false;
  Tool:=nil;
  CleanPos:=0;
  Node:=nil;
  Filename:=TrimFilename(Msg.GetFullFilename);
  if (not FilenameIsAbsolute(Filename)) and (not (mlfTestBuildFile in Msg.Flags)) then exit;
  Code:=CodeToolBoss.LoadFile(Filename,true,false);
  if Code=nil then exit;
  CodeToolBoss.Explore(Code,Tool,false);
  if Tool=nil then exit;
  if Tool.CaretToCleanPos(CodeXYPosition(Msg.Column,Msg.Line,Code),CleanPos)<>0 then exit;
  Node:=Tool.FindDeepestNodeAtPos(CleanPos,false);
  Result:=Node<>nil;
end;

function GetMsgSrcPosOfIdentifier(Msg: TMessageLine; out Identifier: string;
  out Code: TCodeBuffer; out Tool: TCodeTool; out CleanPos: integer; out
  Node: TCodeTreeNode): boolean;
begin
  Result:=false;
  Code:=nil;
  Tool:=nil;
  CleanPos:=0;
  Node:=nil;
  // check if message position is at end of identifier
  // (FPC gives position of start or end of identifier)
  if not GetMsgCodetoolPos(Msg,Code,Tool,CleanPos,Node) then exit;
  Tool.MoveCursorToCleanPos(CleanPos);
  if (CleanPos>Tool.SrcLen) or (not IsIdentChar[Tool.Src[CleanPos]]) then
    Tool.ReadPriorAtom
  else
    Tool.ReadNextAtom;
  Identifier:=Tool.GetAtom;
  CleanPos:=Tool.CurPos.StartPos;
  Result:=IsValidIdent(Identifier);
end;

function GetMsgSrcPosOfThisIdentifier(Msg: TMessageLine; const Identifier: string;
  out Code: TCodeBuffer; out Tool: TCodeTool; out CleanPos: integer;
  out Node: TCodeTreeNode): boolean;
var
  CurIdentifier: string;
begin
  Result:=GetMsgSrcPosOfIdentifier(Msg,CurIdentifier,Code,Tool,CleanPos,Node)
     and (CompareIdentifiers(PChar(CurIdentifier),PChar(Identifier))=0);
end;

{ TQuickFixInheritedMethodIsHidden_AddModifier }

function TQuickFixInheritedMethodIsHidden_AddModifier.IsApplicable(
  Msg: TMessageLine; out MsgID: integer): boolean;
var
  Value1, Value2: string;
begin
  Result:=false;
  MsgID:=0;
  if (not Msg.HasSourcePosition) then exit;
  if IDEFPCParser.MsgLineIsId(Msg,3057,Value1,Value2) then begin
    // An inherited method is hidden by "$1;"
    MsgID:=3057;
    Result:=true
  end
  else if (Msg.SubTool=SubToolPas2js) and (Msg.MsgID=3021) then begin
    // function hides identifier at "$1". Use overload or reintroduce
    MsgID:=3021;
    Result:=true;
  end
  else if (Msg.SubTool=SubToolPas2js) and (Msg.MsgID=3077) then begin
    // Method "$1" hides method of base type "$2" at $3
    MsgID:=3077;
    Result:=true;
  end;
end;

procedure TQuickFixInheritedMethodIsHidden_AddModifier.CreateMenuItems(
  Fixes: TMsgQuickFixes);
var
  i, MsgID: Integer;
  Msg: TMessageLine;
begin
  for i:=0 to Fixes.LineCount-1 do begin
    Msg:=Fixes.Lines[i];
    if not IsApplicable(Msg,MsgID) then continue;
    if ((Msg.SubTool=SubToolFPC) and (MsgID=3057))
    or ((Msg.SubTool=SubToolPas2js) and (MsgID=3077)) then
      Fixes.AddMenuItem(Self, Msg, lisAddModifierOverride, 3);
    Fixes.AddMenuItem(Self,Msg,lisAddModifierOverload,1);
    Fixes.AddMenuItem(Self,Msg,lisAddModifierReintroduce,2);
  end;
end;

procedure TQuickFixInheritedMethodIsHidden_AddModifier.QuickFix(
  Fixes: TMsgQuickFixes; Msg: TMessageLine);
var
  MsgID: integer;
  Code: TCodeBuffer;
  OldChange: Boolean;
  aModifier: String;
begin
  if not IsApplicable(Msg,MsgID) then begin
    debugln(['TQuickFixInheritedMethodIsHidden_AddOverload.QuickFix invalid message ',Msg.Msg]);
    exit;
  end;

  if not LazarusIDE.BeginCodeTools then begin
    DebugLn(['TQuickFixInheritedMethodIsHidden_AddOverload failed because IDE busy']);
    exit;
  end;

  Code:=CodeToolBoss.LoadFile(Msg.GetFullFilename,true,false);
  if Code=nil then exit;

  OldChange:=LazarusIDE.OpenEditorsOnCodeToolChange;
  LazarusIDE.OpenEditorsOnCodeToolChange:=true;
  try
    case Fixes.CurrentCommand.Tag of
    2: aModifier:='reintroduce';
    3: aModifier:='override';
    else aModifier:='overload';
    end;

    if not CodeToolBoss.AddProcModifier(Code,Msg.Column,Msg.Line,aModifier) then
    begin
      DebugLn(['TQuickFixInheritedMethodIsHidden_AddOverload AddProcModifier failed']);
      LazarusIDE.DoJumpToCodeToolBossError;
      exit;
    end;

    // success
    Msg.MarkFixed;
  finally
    LazarusIDE.OpenEditorsOnCodeToolChange:=OldChange;
  end;
end;

{ TQuickFix_HideWithCompilerDirective }

function TQuickFix_HideWithCompilerDirective.IsApplicable(Msg: TMessageLine;
  out MsgID: integer; out Tool: TCodeTool): boolean;
var
  CleanPos: integer;
  Node: TCodeTreeNode;
  Code: TCodeBuffer;
begin
  Result:=false;
  MsgID:=0;
  Tool:=nil;
  if (Msg.Urgency>=mluError)
  or ((Msg.SubTool<>SubToolFPC) and (Msg.SubTool<>SubToolPas2js))
  or (Msg.MsgID=0)
  then exit;
  MsgID:=Msg.MsgID;
  GetMsgCodetoolPos(Msg,Code,Tool,CleanPos,Node);
  Result:=(Tool<>nil);
end;

procedure TQuickFix_HideWithCompilerDirective.CreateMenuItems(
  Fixes: TMsgQuickFixes);
var
  i, MsgID: Integer;
  Msg: TMessageLine;
  Tool: TCodeTool;
  aCaption: String;
begin
  for i:=0 to Fixes.LineCount-1 do begin
    Msg:=Fixes.Lines[i];
    if not IsApplicable(Msg,MsgID,Tool) then continue;
    aCaption:=Format(lisHideMessageByInsertingWarnOffToUnit, [IntToStr(MsgID),
      ExtractFilename(Tool.MainFilename)]);
    Fixes.AddMenuItem(Self,Msg,aCaption);
  end;
end;

procedure TQuickFix_HideWithCompilerDirective.QuickFix(Fixes: TMsgQuickFixes;
  Msg: TMessageLine);
var
  MsgID: integer;
  Tool: TCodeTool;
  Code: TCodeBuffer;
  Comment: String;
  OldChange: Boolean;
begin
  if not IsApplicable(Msg,MsgID,Tool) then exit;

  if not LazarusIDE.BeginCodeTools then begin
    DebugLn(['TQuickFix_HideWithCompilerDirective failed because IDE busy']);
    exit;
  end;

  Code:=CodeToolBoss.LoadFile(Tool.MainFilename,true,false);
  if Code=nil then begin
    debugln(['TQuickFix_HideWithCompilerDirective.QuickFix LoadFile failed: ',Tool.MainFilename]);
    exit;
  end;

  OldChange:=LazarusIDE.OpenEditorsOnCodeToolChange;
  LazarusIDE.OpenEditorsOnCodeToolChange:=true;
  try
    Comment:=' : '+TIDEFPCParser.GetFPCMsgPattern(Msg);
    if not CodeToolBoss.AddUnitWarnDirective(Code,IntToStr(MsgID),Comment,false) then
    begin
      DebugLn(['TQuickFix_HideWithCompilerDirective CodeToolBoss.AddUnitWarnDirective failed']);
      LazarusIDE.DoJumpToCodeToolBossError;
      exit;
    end;
  finally
    LazarusIDE.OpenEditorsOnCodeToolChange:=OldChange;
  end;

  // success
  Msg.MarkFixed;
end;

{ TQuickFixLocalVarNotInitialized_AddAssignment }

function TQuickFixLocalVarNotInitialized_AddAssignment.IsApplicable(
  Msg: TMessageLine; out Identifier: string): boolean;
var
  Tool: TCodeTool;
  CleanPos: integer;
  Node: TCodeTreeNode;
  Code: TCodeBuffer;
begin
  Result:=false;
  if (Msg=nil) or (Msg.MsgID<1)
  or ((Msg.SubTool<>SubToolFPC) and (Msg.SubTool<>SubToolPas2js))
  or (not Msg.HasSourcePosition) then exit;

  // Check: Local variable "$1" does not seem to be initialized
  case Msg.MsgID of
  5036, // W_Local variable "$1" does not seem to be initialized
  5037, // W_Variable "$1" does not seem to be initialized
  5057, // H_Local variable "$1" does not seem to be initialized
  5058, // H_Variable "$1" does not seem to be initialized
  5089, // W_Local variable "$1" of a managed type does not seem to be initialized
  5090, // W_Variable "$1" of a managed type does not seem to be initialized
  5091, // H_Local variable "$1" of a managed type does not seem to be initialized
  5092: // H_Variable "$1" of a managed type does not seem to be initialized
    begin
      Identifier:=TIDEFPCParser.GetFPCMsgValue1(Msg);
      // check if message position is at end of identifier
      if not GetMsgSrcPosOfThisIdentifier(Msg,Identifier,Code,Tool,CleanPos,Node)
      then exit;
    end;
  5059, // W_Function result variable does not seem to initialized
  5060, // H_Function result variable does not seem to be initialized
  5093, // W_function result variable of a managed type does not seem to initialized
  5094: // H_Function result variable of a managed type does not seem to be initialized
    begin
      if not GetMsgSrcPosOfIdentifier(Msg,Identifier,Code,Tool,CleanPos,Node)
      then exit;
    end;
  else
    exit;
  end;
  if not IsValidIdent(Identifier) then exit;

  // check if identifier is in statement and start of expression
  if not (Node.Desc in AllPascalStatements) then exit;
  if (Tool.CurPos.Flag in [cafPoint,cafRoundBracketClose,cafEdgedBracketClose,
                           cafEnd])
  then exit;
  Result:=true;
end;

procedure TQuickFixLocalVarNotInitialized_AddAssignment.CreateMenuItems(
  Fixes: TMsgQuickFixes);
var
  Msg: TMessageLine;
  Identifier: String;
  i: Integer;
begin
  for i:=0 to Fixes.LineCount-1 do begin
    Msg:=Fixes.Lines[i];
    if not IsApplicable(Msg,Identifier) then continue;
    Fixes.AddMenuItem(Self, Msg, Format(lisInsertAssignment, [Identifier]));
    exit;
  end;
end;

procedure TQuickFixLocalVarNotInitialized_AddAssignment.QuickFix(
  Fixes: TMsgQuickFixes; Msg: TMessageLine);
var
  Identifier: String;
  Code: TCodeBuffer;
begin
  if not IsApplicable(Msg,Identifier) then exit;

  if not LazarusIDE.BeginCodeTools then begin
    DebugLn(['TQuickFixLocalVarNotInitialized_AddAssignment failed because IDE busy']);
    exit;
  end;

  Code:=CodeToolBoss.LoadFile(Msg.GetFullFilename,true,false);
  if Code=nil then exit;

  if QuickFixLocalVarNotInitialized(Code, Msg.Column, Msg.Line) then
    Msg.MarkFixed;
end;

{ TQuickFixSrcPathOfPkgContains_OpenPkg }

function TQuickFixSrcPathOfPkgContains_OpenPkg.IsApplicable(Msg: TMessageLine;
  out PkgName: string): boolean;
var
  Dir: string;
  Pattern: String;
  p: SizeInt;
begin
  Result:=false;
  if Msg=nil then exit;
  if Msg.MsgID<>0 then exit;

  Pattern:=lisOtherSourcesPathOfPackageContainsDirectoryWhichIsA;
  p:=Pos('%s',Pattern);
  if p<1 then begin
    debugln(['TQuickFixSrcPathOfPkgContains_OpenPkg.IsApplicable resourcestring misses %s: lisOtherSourcesPathOfPackageContainsDirectoryWhichIsA=',lisOtherSourcesPathOfPackageContainsDirectoryWhichIsA]);
    exit;
  end;
  ReplaceSubstring(Pattern,p,2,'$1');
  p:=Pos('%s',Pattern);
  if p<1 then begin
    debugln(['TQuickFixSrcPathOfPkgContains_OpenPkg.IsApplicable resourcestring misses %s: lisOtherSourcesPathOfPackageContainsDirectoryWhichIsA=',lisOtherSourcesPathOfPackageContainsDirectoryWhichIsA]);
    exit;
  end;
  ReplaceSubstring(Pattern,p,2,'$2');

  if not GetFPCMsgValuesTwo(Msg.Msg,Pattern,PkgName,Dir) then exit;
  if PkgName='' then exit;
  PkgName:=GetIdentifier(PChar(PkgName));
  Result:=IsValidIdent(PkgName);
end;

procedure TQuickFixSrcPathOfPkgContains_OpenPkg.CreateMenuItems(
  Fixes: TMsgQuickFixes);
var
  i: Integer;
  Msg: TMessageLine;
  PkgName: string;
begin
  for i:=0 to Fixes.LineCount-1 do begin
    Msg:=Fixes.Lines[i];
    if not IsApplicable(Msg,PkgName) then continue;
    Fixes.AddMenuItem(Self, Msg, 'Open package "'+PkgName+'"');
    exit;
  end;
end;

procedure TQuickFixSrcPathOfPkgContains_OpenPkg.QuickFix(Fixes: TMsgQuickFixes;
  Msg: TMessageLine);
var
  PkgName: string;
begin
  if not IsApplicable(Msg,PkgName) then exit;
  PackageEditingInterface.DoOpenPackageWithName(PkgName,[pofAddToRecent],false);
end;

{ TQuickFix_HideWithCompilerOption }

function TQuickFix_HideWithCompilerOption.IsApplicable(Msg: TMessageLine; out
  ToolData: TIDEExternalToolData; out IDETool: TObject): boolean;
begin
  Result:=false;
  ToolData:=nil;
  IDETool:=nil;
  if (Msg.Urgency>=mluError)
  or ((Msg.SubTool<>SubToolFPC) and (Msg.SubTool<>SubToolPas2js))
  or (Msg.MsgID=0)
  then exit;
  ToolData:=Msg.GetToolData;
  if ToolData=nil then exit;
  IDETool:=ExternalToolList.GetIDEObject(ToolData);
  Result:=IDETool<>nil;
end;

procedure TQuickFix_HideWithCompilerOption.CreateMenuItems(Fixes: TMsgQuickFixes
  );
var
  i: Integer;
  Msg: TMessageLine;
  IDETool: TObject;
  s: String;
  ToolData: TIDEExternalToolData;
  CompOpts: TLazCompilerOptions;
begin
  for i:=0 to Fixes.LineCount-1 do begin
    Msg:=Fixes.Lines[i];
    if not IsApplicable(Msg,ToolData,IDETool) then continue;
    if IDETool is TLazProject then begin
      CompOpts:=TLazProject(IDETool).LazCompilerOptions;
      if CompOpts.MessageFlags[Msg.MsgID]=cfvHide then exit;
      s:=Format(lisHideWithProjectOptionVm, [IntToStr(Msg.MsgID)])
    end else if IDETool is TIDEPackage then begin
      CompOpts:=TIDEPackage(IDETool).LazCompilerOptions;
      if CompOpts.MessageFlags[Msg.MsgID]=cfvHide then exit;
      s:=Format(lisHideWithPackageOptionVm, [IntToStr(Msg.MsgID)]);
    end else
      continue;
    Fixes.AddMenuItem(Self,Msg,s);
  end;
  inherited CreateMenuItems(Fixes);
end;

procedure TQuickFix_HideWithCompilerOption.QuickFix(Fixes: TMsgQuickFixes;
  Msg: TMessageLine);
var
  IDETool: TObject;
  CompOpts: TLazCompilerOptions;
  Pkg: TIDEPackage;
  ToolData: TIDEExternalToolData;
  i: Integer;
  CurMsg: TMessageLine;
begin
  if not IsApplicable(Msg,ToolData,IDETool) then exit;
  if IDETool is TLazProject then begin
    CompOpts:=TLazProject(IDETool).LazCompilerOptions;
    CompOpts.MessageFlags[Msg.MsgID]:=cfvHide;
  end else if IDETool is TIDEPackage then begin
    if PackageEditingInterface.DoOpenPackageFile(ToolData.Filename,
                                        [pofAddToRecent],false)<>mrOk then exit;
    Pkg:=PackageEditingInterface.FindPackageWithName(ToolData.ModuleName);
    if Pkg=nil then exit;
    CompOpts:=Pkg.LazCompilerOptions;
    CompOpts.MessageFlags[Msg.MsgID]:=cfvHide;
  end else
    exit;
  Msg.MarkFixed;
  // mark all lines of the View with the same message type
  for i:=0 to Msg.Lines.Count-1 do begin
    CurMsg:=Msg.Lines[i];
    if (CurMsg.MsgID<>Msg.MsgID)
    or (CurMsg.Urgency>=mluError)
    or ((CurMsg.SubTool<>SubToolFPC) and (CurMsg.SubTool<>SubToolPas2js))
    then continue;
    CurMsg.MarkFixed;
  end;
end;

{ TQuickFixLocalVariableNotUsed_Remove }

function TQuickFixLocalVariableNotUsed_Remove.IsApplicable(Msg: TMessageLine;
  out Identifier: string): boolean;
var
  Tool: TCodeTool;
  CleanPos: integer;
  Node: TCodeTreeNode;
  Dummy: string;
  Code: TCodeBuffer;
begin
  Result:=false;
  // Check: Local variable "$1" not used
  if IDEFPCParser.MsgLineIsId(Msg,5025,Identifier,Dummy)
  or IDEPas2jsParser.MsgLineIsId(Msg,5025,Identifier,Dummy) then
    // ok
  else
    exit;
  if not Msg.HasSourcePosition or not IsValidIdent(Identifier) then exit;

  // check if message position is at end of identifier
  if not GetMsgSrcPosOfThisIdentifier(Msg,Identifier,Code,Tool,CleanPos,Node) then exit;

  // check if identifier is a var definition
  if not (Node.Desc in [ctnVarDefinition]) then exit;
  Tool.ReadPriorAtom;
  if (Tool.CurPos.Flag in [cafPoint,cafRoundBracketClose,cafEdgedBracketClose,
                           cafEnd])
  then exit;
  Result:=true;
end;

procedure TQuickFixLocalVariableNotUsed_Remove.CreateMenuItems(
  Fixes: TMsgQuickFixes);
var
  Msg: TMessageLine;
  Identifier: String;
  i: Integer;
begin
  for i:=0 to Fixes.LineCount-1 do begin
    Msg:=Fixes.Lines[i];
    if not IsApplicable(Msg,Identifier) then continue;
    Fixes.AddMenuItem(Self, Msg, Format(lisRemoveLocalVariable3, [Identifier]));
    exit;
  end;
end;

procedure TQuickFixLocalVariableNotUsed_Remove.QuickFix(Fixes: TMsgQuickFixes;
  Msg: TMessageLine);
var
  Identifier: String;
  Code: TCodeBuffer;
  OldChange: Boolean;
begin
  if not IsApplicable(Msg,Identifier) then exit;

  if not LazarusIDE.BeginCodeTools then begin
    DebugLn(['TQuickFixLocalVariableNotUsed_Remove failed because IDE busy']);
    exit;
  end;

  Code:=CodeToolBoss.LoadFile(Msg.GetFullFilename,true,false);
  if Code=nil then exit;

  OldChange:=LazarusIDE.OpenEditorsOnCodeToolChange;
  LazarusIDE.OpenEditorsOnCodeToolChange:=true;
  try
    if not CodeToolBoss.RemoveIdentifierDefinition(Code,Msg.Column,Msg.Line) then
    begin
      DebugLn(['TQuickFixLocalVariableNotUsed_Remove remove failed']);
      LazarusIDE.DoJumpToCodeToolBossError;
      exit;
    end;
  finally
    LazarusIDE.OpenEditorsOnCodeToolChange:=OldChange;
  end;

  // message fixed
  Msg.MarkFixed;
end;

{ TQuickFixClassWithAbstractMethods }

function TQuickFixClassWithAbstractMethods.IsApplicable(Msg: TMessageLine; out
  aClassName: string): boolean;
var
  Dummy: string;
  Tool: TCodeTool;
  CleanPos: integer;
  Node: TCodeTreeNode;
  MissingMethod: string;
  Code: TCodeBuffer;
begin
  Result:=false;
  if (not Msg.HasSourcePosition) then exit;
  if IDEFPCParser.MsgLineIsId(Msg,4046,aClassname,Dummy)
  or IDEPas2jsParser.MsgLineIsId(Msg,4046,aClassname,Dummy) then begin
    // Constructing a class "$1" with abstract method "$2"
    Result:=true;
  end else if IDEFPCParser.MsgLineIsId(Msg,5042,MissingMethod,Dummy)
  or IDEFPCParser.MsgLineIsId(Msg,5042,MissingMethod,Dummy) then begin
    // No matching implementation for interface method "$1" found
    // The position is on the 'class' keyword
    // The MissingMethod is 'interfacename.procname'
    if not GetMsgCodetoolPos(Msg,Code,Tool,CleanPos,Node) then exit;
    if not (Node.Desc in AllClassObjects) then exit;
    aClassName:=Tool.ExtractClassName(Node,false);
    Result:=aClassName<>'';
  end;
end;

procedure TQuickFixClassWithAbstractMethods.CreateMenuItems(
  Fixes: TMsgQuickFixes);
var
  Msg: TMessageLine;
  aClassName: string;
  i: Integer;
begin
  for i:=0 to Fixes.LineCount-1 do begin
    Msg:=Fixes.Lines[i];
    if not IsApplicable(Msg,aClassName) then continue;
    Fixes.AddMenuItem(Self, Msg, Format(lisShowAbstractMethodsOf, [aClassName])
      );
    exit;
  end;
end;

procedure TQuickFixClassWithAbstractMethods.QuickFix(Fixes: TMsgQuickFixes;
  Msg: TMessageLine);
var
  Code: TCodeBuffer;
  aClassName: string;
  Tool: TCodeTool;
  NewCode: TCodeBuffer;
  NewX: integer;
  NewY: integer;
  NewTopLine: integer;
  CleanPos: integer;
  Node: TCodeTreeNode;
begin
  if not IsApplicable(Msg,aClassName) then exit;

  if not LazarusIDE.BeginCodeTools then begin
    DebugLn(['TQuickFixClassWithAbstractMethods failed because IDE busy']);
    exit;
  end;

  if not GetMsgCodetoolPos(Msg,Code,Tool,CleanPos,Node) then begin
    DebugLn(['TQuickFixClassWithAbstractMethods no tool for ',Msg.GetFullFilename]);
    ShowError('QuickFix: ClassWithAbstractMethods no tool for '+Msg.GetFullFilename);
    exit;
  end;

  if not CodeToolBoss.FindDeclarationOfIdentifier(Code,Msg.Column,Msg.Line,
    @aClassName[1],NewCode,NewX,NewY,NewTopLine)
  then begin
    if CodeToolBoss.ErrorMessage<>'' then begin
      LazarusIDE.DoJumpToCodeToolBossError
    end else begin
      IDEMessageDialog(lisClassNotFound,
        Format(lisClassNotFoundAt, [aClassName, Code.Filename, IntToStr(Msg.Line
          ), IntToStr(Msg.Column)]),
        mtError,[mbCancel]);
    end;
    exit;
  end;
  //DebugLn(['TQuickFixClassWithAbstractMethods Declaration at ',NewCode.Filename,' ',NewX,',',NewY]);

  if LazarusIDE.DoOpenFileAndJumpToPos(NewCode.Filename,
    Point(NewX,NewY),NewTopLine,-1,-1,[])<>mrOK
  then begin
    DebugLn(['TQuickFixClassWithAbstractMethods failed opening ',NewCode.Filename]);
    ShowError('QuickFix: ClassWithAbstractMethods failed opening '+NewCode.Filename);
    exit;
  end;

  ShowAbstractMethodsDialog;
end;

{ TQuickFixUnitNotFound_Remove }

function TQuickFixUnitNotFound_Remove.IsApplicable(Msg: TMessageLine; out
  MissingUnitName, UsedByUnit: string): boolean;
begin
  Result:=false;
  if Msg=nil then exit;
  if (Msg.SubTool=SubToolFPC) then begin
    if Msg.HasSourcePosition
    and ((Msg.MsgID=5023) // Unit "$1" not used in $2
      or (Msg.MsgID=FPCMsgIDCantFindUnitUsedBy) // Can't find unit $1 used by $2
      or (Msg.MsgID=10023)) // Unit $1 was not found but $2 exists
    then
      // ok
    else exit;
  end else if (Msg.SubTool=SubToolPas2js) then begin
    if Msg.HasSourcePosition
    and ((Msg.MsgID=5023) // Unit "$1" not used in $2
      or (Msg.MsgID=3073)) // Can't find unit $1
    then
      // ok
    else exit;
  end else
    exit;

  MissingUnitName:=Msg.Attribute[FPCMsgAttrMissingUnit];
  UsedByUnit:=Msg.Attribute[FPCMsgAttrUsedByUnit];
  if (MissingUnitName='')
  and not IDEFPCParser.GetFPCMsgValues(Msg,MissingUnitName,UsedByUnit) then begin
    debugln(['TQuickFixUnitNotFound_Remove.IsApplicable failed to extract unit names: ',Msg.Msg]);
    exit;
  end;
  Result:=true;
end;

procedure TQuickFixUnitNotFound_Remove.CreateMenuItems(Fixes: TMsgQuickFixes);
var
  Msg: TMessageLine;
  MissingUnitName: string;
  UsedByUnit: string;
  i: Integer;
begin
  for i:=0 to Fixes.LineCount-1 do begin
    Msg:=Fixes.Lines[i];
    if not IsApplicable(Msg,MissingUnitName,UsedByUnit) then continue;
    Fixes.AddMenuItem(Self, Msg, Format(lisRemoveUses, [MissingUnitName]));
    exit;
  end;
end;

procedure TQuickFixUnitNotFound_Remove.QuickFix(Fixes: TMsgQuickFixes;
  Msg: TMessageLine);
var
  MissingUnitName: string;
  SrcUnitName: string;
  Code: TCodeBuffer;
  OldChange: Boolean;
begin
  if not IsApplicable(Msg,MissingUnitName,SrcUnitName) then begin
    debugln(['TQuickFixUnitNotFound_Remove.QuickFix invalid message ',Msg.Msg]);
    exit;
  end;

  if not LazarusIDE.BeginCodeTools then begin
    DebugLn(['TQuickFixUnitNotFound_Remove failed because IDE busy']);
    exit;
  end;

  Code:=CodeToolBoss.LoadFile(Msg.GetFullFilename,true,false);
  if Code=nil then exit;

  OldChange:=LazarusIDE.OpenEditorsOnCodeToolChange;
  LazarusIDE.OpenEditorsOnCodeToolChange:=true;
  try
    if not CodeToolBoss.RemoveUnitFromAllUsesSections(Code,MissingUnitName) then
    begin
      DebugLn(['TQuickFixUnitNotFound_Remove RemoveUnitFromAllUsesSections failed']);
      LazarusIDE.DoJumpToCodeToolBossError;
      exit;
    end;

    // success
    Msg.MarkFixed;
  finally
    LazarusIDE.OpenEditorsOnCodeToolChange:=OldChange;
  end;
end;

{ TQuickFixIdentifierNotFoundAddLocal }

function TQuickFixIdentifierNotFoundAddLocal.IsApplicable(Msg: TMessageLine;
  out Identifier: string): boolean;
var
  Code: TCodeBuffer;
  Tool: TCodeTool;
  CleanPos: integer;
  Node: TCodeTreeNode;
  Dummy: string;
begin
  Result:=false;
  Identifier:='';
  // check: identifier not found "$1"
  if IDEFPCParser.MsgLineIsId(Msg,5000,Identifier,Dummy)
  or IDEPas2jsParser.MsgLineIsId(Msg,3001,Identifier,Dummy) then
    // ok
  else
    exit;
  if not Msg.HasSourcePosition or not IsValidIdent(Identifier) then exit;

  // check if message position is at identifier
  if not GetMsgSrcPosOfThisIdentifier(Msg,Identifier,Code,Tool,CleanPos,Node) then exit;

  // check if identifier is expression start in statement
  if not (Node.Desc in AllPascalStatements) then exit;
  if (Tool.CurPos.Flag in [cafPoint,cafRoundBracketClose,cafEdgedBracketClose,
                           cafEnd])
  then exit;
  Result:=true;
end;

procedure TQuickFixIdentifierNotFoundAddLocal.CreateMenuItems(
  Fixes: TMsgQuickFixes);
var
  Msg: TMessageLine;
  Identifier: String;
  i: Integer;
begin
  for i:=0 to Fixes.LineCount-1 do begin
    Msg:=Fixes.Lines[i];
    if not IsApplicable(Msg,Identifier) then continue;
    Fixes.AddMenuItem(Self, Msg, Format(lisCreateLocalVariable, [Identifier]));
    // ToDo: add private/public variable
    exit;
  end;
end;

procedure TQuickFixIdentifierNotFoundAddLocal.QuickFix(Fixes: TMsgQuickFixes;
  Msg: TMessageLine);
var
  Identifier: String;
  Code: TCodeBuffer;
  NewCode: TCodeBuffer;
  NewX: integer;
  NewY: integer;
  NewTopLine: integer;
  OldChange: Boolean;
begin
  if not IsApplicable(Msg,Identifier) then exit;

  if not LazarusIDE.BeginCodeTools then begin
    DebugLn(['TQuickFixIdentifierNotFoundAddLocal.Execute failed because IDE busy']);
    exit;
  end;

  Code:=CodeToolBoss.LoadFile(Msg.GetFullFilename,true,false);
  if Code=nil then exit;

  OldChange:=LazarusIDE.OpenEditorsOnCodeToolChange;
  LazarusIDE.OpenEditorsOnCodeToolChange:=true;
  try
    if not CodeToolBoss.CreateVariableForIdentifier(Code,Msg.Column,Msg.Line,-1,
               NewCode,NewX,NewY,NewTopLine,False)
    then begin
      LazarusIDE.DoJumpToCodeToolBossError;
      exit;
    end;
  finally
    LazarusIDE.OpenEditorsOnCodeToolChange:=OldChange;
  end;

  // success
  Msg.MarkFixed;
end;

{ TQuickFix_HideWithIDEDirective }

procedure TQuickFix_HideWithIDEDirective.QuickFix(Fixes: TMsgQuickFixes; Msg: TMessageLine);
var
  Code: TCodeBuffer;

  procedure Fix(CurMsg: TMessageLine);
  var
    p: integer;
    aFilename: String;
  begin
    aFilename:=CurMsg.GetFullFilename;
    if (Code=nil) or (CompareFilenames(aFilename,Code.Filename)<>0) then begin
      Code:=CodeToolBoss.LoadFile(aFilename,true,false);
      if Code=nil then begin
        DebugLn(['TQuickFix_Hide.MenuItemClick ']);
        // ToDo: IDEMessageDialog
        exit;
      end;
    end;
    Code.LineColToPosition(CurMsg.Line,CurMsg.Column,p);
    if p<1 then begin
      DebugLn(['TQuickFix_Hide failed because invalid line, column']);
      {IDEMessageDialog(lisCCOErrorCaption,
        Format(lisInvalidLineColumnInMessage, [LineEnding, Msg.Msg]),
        mtError, [mbCancel]);}
      exit;
    end;

    debugln(['TQuickFix_Hide.MenuItemClick ',Code.Filename,' ',CurMsg.Line,',',CurMsg.Column]);
    Code.Insert(p,'{%H-}');
    CurMsg.Flags:=CurMsg.Flags+[mlfHiddenByIDEDirectiveValid,mlfHiddenByIDEDirective];
    CurMsg.MarkFixed;
  end;

var
  Tree: TAvlTree;
  Node: TAvlTreeNode;
  i: Integer;
  OldChange: Boolean;
begin
  OldChange:=LazarusIDE.OpenEditorsOnCodeToolChange;
  LazarusIDE.OpenEditorsOnCodeToolChange:=true;
  Tree:=TAvlTree.Create(@CompareMsgLinesSrcPos);
  try
    // get all messages to hide and sort them for position
    if Msg=nil then begin
      for i:=0 to Fixes.LineCount-1 do begin
        Msg:=Fixes.Lines[i];
        if not IsApplicable(Msg) then continue;
        Tree.Add(Msg);
      end;
    end else if IsApplicable(Msg) then
      Tree.Add(Msg);
    if Tree.Count=0 then exit;

    {if not LazarusIDE.BeginCodeTools then begin
      DebugLn(['TQuickFix_Hide failed because IDE busy']);
      exit;
    end;}

    // insert marks beginning with the highest line,column
    Code:=nil;
    Node:=Tree.FindHighest;
    while Node<>nil do begin
      Msg:=TMessageLine(Node.Data);
      Fix(Msg);
      Node:=Node.Precessor;
    end;
  finally
    LazarusIDE.OpenEditorsOnCodeToolChange:=OldChange;
    Tree.Free;
  end;
end;

function TQuickFix_HideWithIDEDirective.IsApplicable(Msg: TMessageLine): boolean;
begin
  Result:=false;
  if (Msg.Urgency>=mluError)
  or ((Msg.SubTool<>SubToolFPC) and (Msg.SubTool<>SubToolPas2js))
  or (not Msg.HasSourcePosition)
  or (mlfHiddenByIDEDirective in Msg.Flags)
  then exit;
  Result:=true;
end;

procedure TQuickFix_HideWithIDEDirective.CreateMenuItems(Fixes: TMsgQuickFixes);
var
  Msg: TMessageLine;
  i: Integer;
  List: TFPList;
  aCaption: String;
  aFilename: String;
  MultiFile: Boolean;
begin
  List:=TFPList.Create;
  try
    MultiFile:=false;
    aFilename:='';
    for i:=0 to Fixes.LineCount-1 do begin
      Msg:=Fixes.Lines[i];
      if not IsApplicable(Msg) then continue;
      if aFilename='' then
        aFilename:=Msg.GetFullFilename
      else if CompareFilenames(aFilename,Msg.GetFullFilename)<>0 then
        MultiFile:=true;
      List.Add(Msg);
    end;
    if List.Count=0 then exit;
    if List.Count>1 then
      Fixes.AddMenuItem(Self, nil,
        lisHideAllHintsAndWarningsByInsertingIDEDirectivesH);

    for i:=0 to List.Count-1 do begin
      Msg:=TMessageLine(List[i]);
      if MultiFile then
        aCaption:=Msg.GetShortFilename
      else
        aCaption:='';
      if List.Count>1 then
        aCaption+='('+IntToStr(Msg.Line)+','+IntToStr(Msg.Column)+')';
      if aCaption<>'' then
        aCaption:=Format(lisHideMessageAtByInsertingIDEDirectiveH, [aCaption])
      else
        aCaption:=lisHideMessageByInsertingIDEDirectiveH;
      Fixes.AddMenuItem(Self,Msg,aCaption);
    end;
  finally
    List.Free;
  end;
end;

{ TIDEQuickFixes }

procedure TIDEQuickFixes.MenuItemClick(Sender: TObject);
var
  i: Integer;
  Info: TMenuItemInfo;
  ListsMsgLines: TFPList;
  MsgLines: TMessageLines;
  Cmd: TIDEMenuCommand;
begin
  Cmd:=Sender as TIDEMenuCommand;
  Info:=TMenuItemInfo(fMenuItemToInfo[Cmd]);
  if Info=nil then exit;
  FCurrentSender:=Sender;
  FCurrentCommand:=Cmd;
  try
    Info.Fix.QuickFix(Self,Info.Msg);
  finally
    ListsMsgLines:=TFPList.Create;
    try
      for i:=0 to LineCount-1 do begin
        MsgLines:=Lines[i].Lines;
        if ListsMsgLines.IndexOf(MsgLines)>=0 then continue;
        ListsMsgLines.Add(MsgLines);
      end;
      for i:=0 to ListsMsgLines.Count-1 do
        TMessageLines(ListsMsgLines[i]).ApplyFixedMarks;
    finally
      FCurrentSender:=nil;
      FCurrentCommand:=nil;
      ListsMsgLines.Free;
    end;
  end;
end;

constructor TIDEQuickFixes.Create(aOwner: TComponent);
begin
  inherited Create(aOwner);
  IDEQuickFixes:=Self;
  MsgQuickFixes:=Self;
  fMenuItemToInfo:=TPointerToPointerTree.Create;

  // init standard quickfixes
  // add them in the order of usefulness
  IDEQuickFixes.RegisterQuickFix(TQuickFixIdentifierNotFoundAddLocal.Create);
  IDEQuickFixes.RegisterQuickFix(TQuickFixLocalVariableNotUsed_Remove.Create);
  IDEQuickFixes.RegisterQuickFix(TQuickFixLocalVarNotInitialized_AddAssignment.Create);
  IDEQuickFixes.RegisterQuickFix(TQuickFixUnitNotFound_Remove.Create);
  IDEQuickFixes.RegisterQuickFix(TQuickFixClassWithAbstractMethods.Create);
  IDEQuickFixes.RegisterQuickFix(TQuickFixSrcPathOfPkgContains_OpenPkg.Create);
  IDEQuickFixes.RegisterQuickFix(TQuickFixInheritedMethodIsHidden_AddModifier.Create);

  // add as last (no fix, just hide message)
  IDEQuickFixes.RegisterQuickFix(TQuickFix_HideWithIDEDirective.Create);
  IDEQuickFixes.RegisterQuickFix(TQuickFix_HideWithCompilerDirective.Create);
  IDEQuickFixes.RegisterQuickFix(TQuickFix_HideWithCompilerOption.Create);
end;

destructor TIDEQuickFixes.Destroy;
begin
  fMenuItemToInfo.ClearWithFree;
  FreeAndNil(fMenuItemToInfo);
  MsgQuickFixes:=nil;
  IDEQuickFixes:=nil;
  inherited Destroy;
end;

procedure TIDEQuickFixes.OnPopupMenu(aParentMenuItem: TIDEMenuSection);
var
  i: Integer;
begin
  ParentMenuItem:=aParentMenuItem;
  try
    if LineCount=0 then exit;
    for i:=0 to Count-1 do
      Items[i].CreateMenuItems(Self);
  finally
    ParentMenuItem:=nil;
  end;
end;

procedure TIDEQuickFixes.SetMsgLines(aMsg: TMessageLine);
begin
  ClearLines;
  if aMsg<>nil then
    fMsg.Add(aMsg);
end;

procedure TIDEQuickFixes.AddMsgLine(aMsg: TMessageLine);
begin
  if (aMsg<>nil) and (fMsg.IndexOf(aMsg)<0) then
    fMsg.Add(aMsg);
end;

procedure TIDEQuickFixes.ClearLines;
var
  i: Integer;
begin
  fMenuItemToInfo.ClearWithFree;
  for i:=ComponentCount-1 downto 0 do
    if Components[i] is TMenuItem then
      Components[i].Free;
  fMsg.Clear;
end;

function TIDEQuickFixes.AddMenuItem(Fix: TMsgQuickFix; Msg: TMessageLine;
  aCaption: string; aTag: PtrInt): TIDEMenuCommand;
var
  Info: TMenuItemInfo;
begin
  if (Fix=nil) then
    raise Exception.Create('missing Fix');
  if (aCaption='') then
    raise Exception.Create('missing Caption');
  if (ParentMenuItem.Count>50) then exit(nil);
  Result:=RegisterIDEMenuCommand(ParentMenuItem,
    'MsgQuickFix'+IntToStr(ParentMenuItem.Count),aCaption,@MenuItemClick);
  Result.Tag:=aTag;
  Info:=TMenuItemInfo.Create;
  Info.Fix:=Fix;
  Info.Msg:=Msg;
  Info.MenuItem:=Result;
  fMenuItemToInfo[Result]:=Info;
end;

function TIDEQuickFixes.OpenMsg(Msg: TMessageLine): boolean;
var
  i: Integer;
begin
  Result:=false;
  if Msg=nil then exit;
  for i:=0 to Count-1 do begin
    Items[i].JumpTo(Msg,Result);
    if Result then exit;
  end;
end;

end.

