ide: find/rename identifier: allow conflict and check if FNode is valid

This commit is contained in:
mattias 2025-02-11 16:17:18 +01:00
parent f83078ff61
commit 8726dd48e7
4 changed files with 98 additions and 79 deletions

View File

@ -280,7 +280,7 @@ type
// initializing single codetool // initializing single codetool
function GetCodeToolForSource(Code: TCodeBuffer; function GetCodeToolForSource(Code: TCodeBuffer;
GoToMainCode, ExceptionOnError: boolean): TCustomCodeTool; GoToMainCode, ExceptionOnError: boolean): TCustomCodeTool;
function FindCodeToolForSource(Code: TCodeBuffer): TCustomCodeTool; function FindCodeToolForSource(Code: TCodeBuffer): TCodeTool;
property CurCodeTool: TCodeTool read FCurCodeTool; property CurCodeTool: TCodeTool read FCurCodeTool;
procedure ClearCurCodeTool; procedure ClearCurCodeTool;
function InitCurCodeTool(Code: TCodeBuffer): boolean; function InitCurCodeTool(Code: TCodeBuffer): boolean;
@ -6692,7 +6692,7 @@ begin
end; end;
function TCodeToolManager.FindCodeToolForSource(Code: TCodeBuffer function TCodeToolManager.FindCodeToolForSource(Code: TCodeBuffer
): TCustomCodeTool; ): TCodeTool;
var var
ANode: TAVLTreeNode; ANode: TAVLTreeNode;
CurSrc, SearchedSrc: Pointer; CurSrc, SearchedSrc: Pointer;
@ -6706,7 +6706,7 @@ begin
else if CurSrc<SearchedSrc then else if CurSrc<SearchedSrc then
ANode:=ANode.Right ANode:=ANode.Right
else begin else begin
Result:=TCustomCodeTool(ANode.Data); Result:=TCodeTool(ANode.Data);
exit; exit;
end; end;
end; end;

View File

@ -212,7 +212,6 @@ type
out CleanCursorPos: integer); out CleanCursorPos: integer);
function StringIsKeyWord(const Word: string): boolean; function StringIsKeyWord(const Word: string): boolean;
function IsStringKeyWord(const Word: string): boolean;
// cursor moving // cursor moving
procedure MoveCursorToNodeStart(ANode: TCodeTreeNode); {$IFDEF UseInline}inline;{$ENDIF} procedure MoveCursorToNodeStart(ANode: TCodeTreeNode); {$IFDEF UseInline}inline;{$ENDIF}
@ -2406,11 +2405,6 @@ begin
Result:=(Word<>'') and IsIdentStartChar[Word[1]] Result:=(Word<>'') and IsIdentStartChar[Word[1]]
and WordIsKeyWordFuncList.DoItUpperCase(Word,1,length(Word)); and WordIsKeyWordFuncList.DoItUpperCase(Word,1,length(Word));
end; end;
function TCustomCodeTool.IsStringKeyWord(const Word: string): boolean;
begin
Result:=(Word<>'') and IsIdentStartChar[Word[1]]
and (WordIsKeyWordFuncList.IndexOf(Word, true)>=0);
end;
procedure TCustomCodeTool.MoveCursorToNodeStart(ANode: TCodeTreeNode); procedure TCustomCodeTool.MoveCursorToNodeStart(ANode: TCodeTreeNode);
begin begin

View File

@ -40,7 +40,7 @@ uses
// LazUtils // LazUtils
LazFileUtils, LazFileCache, laz2_DOM, LazStringUtils, AvgLvlTree, LazLoggerBase, LazFileUtils, LazFileCache, laz2_DOM, LazStringUtils, AvgLvlTree, LazLoggerBase,
// IdeIntf // IdeIntf
IdeIntfStrConsts, LazIDEIntf, IDEWindowIntf, SrcEditorIntf, PackageIntf, IdeIntfStrConsts, LazIDEIntf, IDEWindowIntf, SrcEditorIntf, PackageIntf, ProjectIntf,
IDEDialogs, InputHistory, IDEDialogs, InputHistory,
// IdeUtils // IdeUtils
DialogProcs, DialogProcs,
@ -82,10 +82,11 @@ type
FIdentifierPosition: TPoint; FIdentifierPosition: TPoint;
FOldIdentifier: string; FOldIdentifier: string;
FNewIdentifier: string; FNewIdentifier: string;
FForbidden: TStringList; // already defined identifiers in scope FConflictUnitNames: TStringList; // already defined identifiers in scope
FIsPrivate: boolean; FIsPrivate: boolean;
FNode: TCodeTreeNode; FNode: TCodeTreeNode;
FTool: TCustomCodeTool; FNodesDeletedChangeStep: integer;
FTool: TCodeTool;
FFiles: TStringList; FFiles: TStringList;
procedure SetAllowRename(const AValue: boolean); procedure SetAllowRename(const AValue: boolean);
procedure SetIsPrivate(const AValue: boolean); procedure SetIsPrivate(const AValue: boolean);
@ -93,6 +94,7 @@ type
procedure UpdateRename; procedure UpdateRename;
procedure GatherFiles; procedure GatherFiles;
function NewIdentifierIsConflicted(var ErrMsg: string): boolean; function NewIdentifierIsConflicted(var ErrMsg: string): boolean;
function IsNodeInvalid(const Msg: string): boolean;
public public
destructor Destroy; override; destructor Destroy; override;
procedure LoadFromConfig; procedure LoadFromConfig;
@ -582,7 +584,7 @@ begin
if Result<>mrOk then begin if Result<>mrOk then begin
if IsConflicted then if IsConflicted then
IDEMessageDialog(lisRenamingConflict, IDEMessageDialog(lisRenamingConflict,
Format(lisIdentifierWasAlreadyUsed,[Options.RenameTo]), Format(lisIdentifierIsAlreadyUsed,[Options.RenameTo]),
mtError,[mbOK]) mtError,[mbOK])
else else
LazarusIDE.DoJumpToCodeToolBossError; LazarusIDE.DoJumpToCodeToolBossError;
@ -1054,6 +1056,7 @@ var
i: integer; i: integer;
begin begin
if FOldIdentifier='' then exit; if FOldIdentifier='' then exit;
if IsNodeInvalid('TFindRenameIdentifierDialog.ValidateNewName') then exit;
Err:=''; Err:='';
FNewIdentifier:=NewEdit.Text; FNewIdentifier:=NewEdit.Text;
if (FNode<>nil) if (FNode<>nil)
@ -1083,7 +1086,7 @@ begin
break; break;
end; end;
end; end;
ok:=not FTool.IsStringKeyWord(dotPart); ok:=not FTool.StringIsKeyWord(dotPart);
end; end;
if not ok then if not ok then
Err:=Format(lisIdentifierIsReservedWord,[dotPart]); Err:=Format(lisIdentifierIsReservedWord,[dotPart]);
@ -1122,7 +1125,7 @@ begin
ButtonPanel1.OKButton.Caption:=lisFRIRenameAllReferences ButtonPanel1.OKButton.Caption:=lisFRIRenameAllReferences
else else
ButtonPanel1.OKButton.Caption:=lisFRIFindReferences; ButtonPanel1.OKButton.Caption:=lisFRIFindReferences;
if RenameCheckBox.Checked and (FForbidden=nil) then if RenameCheckBox.Checked and (FConflictUnitNames=nil) then
GatherFiles; GatherFiles;
end; end;
@ -1207,7 +1210,7 @@ var
if Result then begin if Result then begin
if anItem.Node<>nil then begin if anItem.Node<>nil then begin
ContextPos:=anItem.Node.StartPos; ContextPos:=anItem.Node.StartPos;
ErrInfo:= Format(lisIdentifierWasAlreadyUsed,[FNewIdentifier]); ErrInfo:= Format(lisIdentifierIsAlreadyUsed,[FNewIdentifier]);
end else begin end else begin
if anItem.ResultType='' then if anItem.ResultType='' then
ErrInfo:= Format(lisIdentifierIsDeclaredCompilerProcedure,[FNewIdentifier]) ErrInfo:= Format(lisIdentifierIsDeclaredCompilerProcedure,[FNewIdentifier])
@ -1216,14 +1219,25 @@ var
end; end;
end; end;
end; end;
begin begin
if RenameCheckBox.Checked then if IsNodeInvalid('TFindRenameIdentifierDialog.FindOrRenameButtonClick') then exit;
ModalResult:=mrNone
else begin if not RenameCheckBox.Checked then begin
// find references
ModalResult:=mrOK; ModalResult:=mrOK;
exit; exit;
end; end;
if CompareDottedIdentifiers(PChar(FNewIdentifier),PChar(FOldIdentifier))=0 then begin
// change all references to same case
ModalResult:=mrOk;
exit;
end;
// rename -> check for conflict
ModalResult:=mrNone;
CTB_IdentComplIncludeKeywords:=CodeToolBoss.IdentComplIncludeKeywords; CTB_IdentComplIncludeKeywords:=CodeToolBoss.IdentComplIncludeKeywords;
CodeToolBoss.IdentComplIncludeKeywords:=false; CodeToolBoss.IdentComplIncludeKeywords:=false;
@ -1234,28 +1248,29 @@ begin
CTB_IdentComplIncludeWords:=CodeToolsOptions.CodeToolsOpts.IdentComplIncludeWords; CTB_IdentComplIncludeWords:=CodeToolsOptions.CodeToolsOpts.IdentComplIncludeWords;
CodeToolsOptions.CodeToolsOpts.IdentComplIncludeWords:=icwIncludeFromAllUnits; CodeToolsOptions.CodeToolsOpts.IdentComplIncludeWords:=icwIncludeFromAllUnits;
ErrInfo:='';
try try
anItem:=nil; anItem:=nil;
isOK:=true;
CodeToolBoss.IdentifierList.Clear; CodeToolBoss.IdentifierList.Clear;
Res:= LoadCodeBuffer(ACodeBuffer,IdentifierFileName,[lbfCheckIfText],false); Res:= LoadCodeBuffer(ACodeBuffer,IdentifierFileName,[lbfCheckIfText],false);
//try declaration context //try declaration context
if Res=mrOK then begin if Res<>mrOK then begin
ModalResult:=mrCancel;
exit;
end;
tmpNode:=FNode; tmpNode:=FNode;
while isOK and (tmpNode<>nil) do begin while tmpNode<>nil do begin
if (tmpNode.Parent<>nil) and (tmpNode.Parent.Desc in AllFindContextDescs) if (tmpNode.Parent<>nil) and (tmpNode.Parent.Desc in AllFindContextDescs)
then begin then begin
ContextPos:=tmpNode.Parent.EndPos;//can point at the end of "end;" ContextPos:=tmpNode.Parent.EndPos;//can point at the end of "end;"
if GetCodePos(ContextPos,X,Y) then if GetCodePos(ContextPos,X,Y) then
CodeToolBoss.GatherIdentifiers(ACodeBuffer, X, Y); CodeToolBoss.GatherIdentifiers(ACodeBuffer, X, Y);
isOK:=not FindConflict; //ErrInfo is set inside the function FindConflict; //ErrInfo is set inside the function
break;
end; end;
tmpNode:=tmpNode.Parent; tmpNode:=tmpNode.Parent;
end; end;
end;
if isOK then
ModalResult:=mrOk;
finally finally
CodeToolBoss.IdentComplIncludeKeywords:= CodeToolBoss.IdentComplIncludeKeywords:=
CTB_IdentComplIncludeKeywords; CTB_IdentComplIncludeKeywords;
@ -1263,19 +1278,13 @@ begin
CTB_CodeCompletionTemplateFileName; CTB_CodeCompletionTemplateFileName;
CodeToolsOptions.CodeToolsOpts.IdentComplIncludeWords:= CodeToolsOptions.CodeToolsOpts.IdentComplIncludeWords:=
CTB_IdentComplIncludeWords; CTB_IdentComplIncludeWords;
if RenameCheckBox.Checked then begin
ButtonPanel1.OKButton.Enabled:=isOK;
if isOK then begin
NewGroupBox.Caption:=lisFRIRenaming;
NewGroupBox.Font.Style:=NewGroupBox.Font.Style-[fsBold];
end else begin
ErrInfo:=StringReplace(ErrInfo,'&','&&',[rfReplaceAll]);
NewGroupBox.Caption:=lisFRIRenaming+' - '+ ErrInfo;
NewGroupBox.Font.Style:=NewGroupBox.Font.Style+[fsBold];
end;
end; end;
if ErrInfo<>'' then begin
if IDEMessageDialog(dlgMsgWinColorUrgentWarning, ErrInfo, mtWarning,
[mbCancel, mbIgnore])<>mrIgnore then
exit;
end; end;
ModalResult:=mrOk;
end; end;
procedure TFindRenameIdentifierDialog.FindRenameIdentifierDialogClose( procedure TFindRenameIdentifierDialog.FindRenameIdentifierDialogClose(
@ -1337,11 +1346,9 @@ procedure TFindRenameIdentifierDialog.SetIdentifier(
const NewIdentifierFilename: string; var NewIdentifierPosition: TPoint); const NewIdentifierFilename: string; var NewIdentifierPosition: TPoint);
var var
s: String; s: String;
ACodeBuffer: TCodeBuffer; ACodeBuffer, CurCode: TCodeBuffer;
ListOfCodeBuffer: TFPList; ListOfCodeBuffer: TFPList;
i: Integer; i: Integer;
CurCode: TCodeBuffer;
Tool: TCodeTool;
CodeXY: TCodeXYPosition; CodeXY: TCodeXYPosition;
CleanPos: integer; CleanPos: integer;
Node: TCodeTreeNode; Node: TCodeTreeNode;
@ -1349,6 +1356,7 @@ begin
FIdentifierFilename:=NewIdentifierFilename; FIdentifierFilename:=NewIdentifierFilename;
FIdentifierPosition:=NewIdentifierPosition; FIdentifierPosition:=NewIdentifierPosition;
FNode:=nil; FNode:=nil;
FTool:=nil;
//debugln(['TFindRenameIdentifierDialog.SetIdentifier ',FIdentifierFilename,' ',dbgs(FIdentifierPosition)]); //debugln(['TFindRenameIdentifierDialog.SetIdentifier ',FIdentifierFilename,' ',dbgs(FIdentifierPosition)]);
CurrentListBox.Items.Clear; CurrentListBox.Items.Clear;
s:=IdentifierFilename s:=IdentifierFilename
@ -1374,25 +1382,26 @@ begin
ListOfCodeBuffer.Free; ListOfCodeBuffer.Free;
end; end;
if CodeToolBoss.GetIdentifierAt(ACodeBuffer,
NewIdentifierPosition.X,NewIdentifierPosition.Y,FOldIdentifier,FNode) then
begin
CurrentGroupBox.Caption:= Format(lisFRIIdentifier,[''])+EnforceAmp(FOldIdentifier);
NewEdit.Text:=FOldIdentifier;
end else
FOldIdentifier:='';
// check if in implementation or private section // check if in implementation or private section
if CodeToolBoss.Explore(ACodeBuffer,Tool,false) then begin if CodeToolBoss.Explore(ACodeBuffer,FTool,false) then begin
CodeXY:=CodeXYPosition(NewIdentifierPosition.X,NewIdentifierPosition.Y,ACodeBuffer); CodeXY:=CodeXYPosition(NewIdentifierPosition.X,NewIdentifierPosition.Y,ACodeBuffer);
if Tool.CaretToCleanPos(CodeXY,CleanPos)=0 then begin if FTool.CaretToCleanPos(CodeXY,CleanPos)=0 then begin
Node:=Tool.BuildSubTreeAndFindDeepestNodeAtPos(CleanPos,false); Node:=FTool.BuildSubTreeAndFindDeepestNodeAtPos(CleanPos,false);
if (Node=nil) if (Node=nil)
or Node.HasParentOfType(ctnImplementation) or Node.HasParentOfType(ctnImplementation)
or Node.HasParentOfType(ctnClassPrivate) then or Node.HasParentOfType(ctnClassPrivate) then
IsPrivate:=true; IsPrivate:=true;
end; end;
end; end;
if CodeToolBoss.GetIdentifierAt(ACodeBuffer,
NewIdentifierPosition.X,NewIdentifierPosition.Y,FOldIdentifier,FNode) then
begin
CurrentGroupBox.Caption:= Format(lisFRIIdentifier,[''])+EnforceAmp(FOldIdentifier);
end else
FOldIdentifier:='';
FNodesDeletedChangeStep:=FTool.NodesDeletedChangeStep;
NewEdit.Text:=FOldIdentifier;
end; end;
procedure TFindRenameIdentifierDialog.GatherFiles; procedure TFindRenameIdentifierDialog.GatherFiles;
var var
@ -1407,10 +1416,11 @@ var
Graph: TUsesGraph; Graph: TUsesGraph;
Node: TAVLTreeNode; Node: TAVLTreeNode;
UGUnit: TUGUnit; UGUnit: TUGUnit;
UnitInfo:TUnitInfo; UnitInfo, ProjFileInfo:TUnitInfo;
Completed: boolean; Completed: boolean;
ExternalProjectName, InternalProjectName, ProjMainFilename: string; ExternalProjectName, InternalProjectName, ProjMainFilename: string;
begin begin
if FConflictUnitNames<>nil then exit;
if not LazarusIDE.BeginCodeTools then exit; if not LazarusIDE.BeginCodeTools then exit;
if Project1=nil then exit; if Project1=nil then exit;
if not AllowRename then exit; if not AllowRename then exit;
@ -1429,16 +1439,18 @@ begin
exit; exit;
end; end;
if IsNodeInvalid('TFindRenameIdentifierDialog.GatherFiles') then exit;
OwnerList:=nil; OwnerList:=nil;
Files:=nil;
try try
FTool:=CodeToolBoss.FindCodeToolForSource(DeclCode); FConflictUnitNames:=TStringList.Create;
FForbidden:=TStringList.Create;
Files:=TStringList.Create; Files:=TStringList.Create;
ProjMainFilename:=Project1.MainFilename; ProjFileInfo:=Project1.MainUnitInfo;
if ProjMainFilename<>'' then begin if ProjFileInfo<>nil then begin
InternalProjectName:= ProjMainFilename:=ProjFileInfo.Filename;
Project1.ProjectUnitWithFilename(ProjMainFilename).Unit_Name; InternalProjectName:=ProjFileInfo.Unit_Name;
ExternalProjectName:=ExtractFileNameOnly(ProjMainFilename); ExternalProjectName:=ExtractFileNameOnly(ProjMainFilename);
if ExternalProjectName<>'' then begin if ExternalProjectName<>'' then begin
// units cannot have filename matching project file name - only warnings/problems, // units cannot have filename matching project file name - only warnings/problems,
@ -1450,12 +1462,12 @@ begin
and (CompareDottedIdentifiers(PChar(ExternalProjectName), and (CompareDottedIdentifiers(PChar(ExternalProjectName),
PChar(InternalProjectName))<>0) PChar(InternalProjectName))<>0)
then then
FForbidden.Add(ExternalProjectName); FConflictUnitNames.Add(ExternalProjectName);
end; end;
end; end;
OwnerList:=TFPList.Create; OwnerList:=TFPList.Create;
OwnerList.Add(LazarusIDE.ActiveProject); OwnerList.Add(Project1);
// get source files of packages and projects // get source files of packages and projects
ExtraFiles:=PackageEditingInterface.GetSourceFilesOfOwners(OwnerList); ExtraFiles:=PackageEditingInterface.GetSourceFilesOfOwners(OwnerList);
@ -1485,7 +1497,7 @@ begin
CurUnitname:=UnitInfo.Unit_Name CurUnitname:=UnitInfo.Unit_Name
else else
CurUnitname:=ExtractFileNameOnly(Files[i]); CurUnitname:=ExtractFileNameOnly(Files[i]);
FForbidden.Add(CurUnitname); //store for ValidateNewName FConflictUnitNames.Add(CurUnitname); //store for ValidateNewName
end; end;
SetFiles(Files); SetFiles(Files);
finally finally
@ -1502,7 +1514,10 @@ begin
Result:=false; Result:=false;
ErrMsg:=''; ErrMsg:='';
if not AllowRename then exit; if not AllowRename then exit;
if not (FNode.Desc in [ctnProgram..ctnUnit,ctnUseUnit,ctnUseUnitNamespace,ctnUseUnitClearName]) if IsNodeInvalid('TFindRenameIdentifierDialog.NewIdentifierIsConflicted') then exit;
if (FNode<>nil)
and not (FNode.Desc in (AllSourceTypes+[ctnUseUnit,ctnUseUnitNamespace,ctnUseUnitClearName]))
and (Pos('.',FNewIdentifier)>0) then and (Pos('.',FNewIdentifier)>0) then
begin begin
ErrMsg:=Format(lisIdentifierCannotBeDotted,[FNewIdentifier]); ErrMsg:=Format(lisIdentifierCannotBeDotted,[FNewIdentifier]);
@ -1512,24 +1527,34 @@ begin
ErrMsg:=lisIdentifierCannotBeEmpty; ErrMsg:=lisIdentifierCannotBeEmpty;
exit(true); exit(true);
end; end;
if FForbidden=nil then exit; if FConflictUnitNames=nil then exit;
i:=0; i:=0;
while (i<=FForbidden.Count-1) and while (i<=FConflictUnitNames.Count-1) and
(CompareDottedIdentifiers(PChar(FNewIdentifier),PChar(FForbidden[i]))<>0) do (CompareDottedIdentifiers(PChar(FNewIdentifier),PChar(FConflictUnitNames[i]))<>0) do
inc(i); inc(i);
Result:= i<=FForbidden.Count-1; Result:= i<=FConflictUnitNames.Count-1;
if Result then begin if Result then begin
ErrMsg:=Format(lisIdentifierWasAlreadyUsed,[FNewIdentifier]); ErrMsg:=Format(lisIdentifierIsAlreadyUsed,[FNewIdentifier]);
exit; exit;
end; end;
// checking if there are existing other identifiers conflited with the new // checking if there are existing other identifiers conflited with the new
// will be executed when "Rename all References" button is clicked // will be executed when "Rename all References" button is clicked
end; end;
function TFindRenameIdentifierDialog.IsNodeInvalid(const Msg: string): boolean;
begin
if FNode=nil then exit(false);
if FTool.NodesDeletedChangeStep=FNodesDeletedChangeStep then exit(false);
Result:=true;
debugln([Msg,' nodes deleted New=',FTool.NodesDeletedChangeStep,' Old=',FNodesDeletedChangeStep]);
FNode:=nil;
ModalResult:=mrCancel;
end;
destructor TFindRenameIdentifierDialog.Destroy; destructor TFindRenameIdentifierDialog.Destroy;
begin begin
FreeAndNil(FForbidden); FreeAndNil(FConflictUnitNames);
FreeAndNil(FFiles); FreeAndNil(FFiles);
inherited Destroy; inherited Destroy;
end; end;

View File

@ -5462,8 +5462,8 @@ resourcestring
lisRenamingAborted = 'Renaming aborted'; lisRenamingAborted = 'Renaming aborted';
lisRenamingConflict = 'Renaming conflict'; lisRenamingConflict = 'Renaming conflict';
lisFileAlreadyExists = 'File "%s" already exists.'; lisFileAlreadyExists = 'File "%s" already exists.';
lisIdentifierWasAlreadyUsed = 'Identifier ' lisIdentifierIsAlreadyUsed = 'Identifier '
+ '"%s" was already used'; + '"%s" is already used';
lisIdentifierCannotBeDotted = 'Identifier "%s" cannot be dotted'; lisIdentifierCannotBeDotted = 'Identifier "%s" cannot be dotted';
lisIdentifierCannotBeEmpty = 'Identifier cannot be empty'; lisIdentifierCannotBeEmpty = 'Identifier cannot be empty';
lisIdentifierIsInvalid = 'Identifier ' lisIdentifierIsInvalid = 'Identifier '