diff --git a/ide/findrenameidentifier.pas b/ide/findrenameidentifier.pas index 7346f30a80..0ce1e22a29 100644 --- a/ide/findrenameidentifier.pas +++ b/ide/findrenameidentifier.pas @@ -45,10 +45,10 @@ uses // IdeUtils DialogProcs, // LazConfig - TransferMacros, IDEProcs, SearchPathProcs, + TransferMacros, IDEProcs, SearchPathProcs, EnvironmentOpts, // IDE LazarusIDEStrConsts, MiscOptions, CodeToolsOptions, SearchResultView, CodeHelp, CustomCodeTool, - FindDeclarationTool, ChangeDeclarationTool, SourceFileManager, Project; + FindDeclarationTool, ChangeDeclarationTool, FileProcs, SourceFileManager, Project; type @@ -220,7 +220,8 @@ var begin Result:=mrOk; OldRefs:=nil; - if CompareFilenames(OldFileName,NewFileName)=0 then exit; + if (CompareFilenames(OldFileName,NewFileName)=0) + and (ExtractFileName(OldFileName)=ExtractFilename(NewFilename)) then exit; anUnitInfo:=nil; if Assigned(Project1) then anUnitInfo:=Project1.UnitInfoWithFilename(OldFileName); @@ -443,11 +444,12 @@ var ExtraFiles: TStrings; Files: TStringList; PascalReferences: TObjectList; // list of TSrcNameRefs - OldChange, Completed, RenamingFile, IsConflicted: Boolean; + OldChange, Completed, MovingFile, RenamingFile, IsConflicted, DoLowercase, Confirm, + NewFileCreated: Boolean; Graph: TUsesGraph; AVLNode: TAVLTreeNode; UGUnit: TUGUnit; - Identifier, NewFilename, OldFileName, ChangedFileType, SrcNamed, lfmString: string; + Identifier, NewFilename, OldFileName, SrcNamed, lfmString, s: string; FindRefFlags: TFindRefsFlags; Tool: TCodeTool; Node: TCodeTreeNode; @@ -460,7 +462,6 @@ begin StartSrcCode:=TCodeBuffer(StartSrcEdit.CodeToolsBuffer); StartTopLine:=StartSrcEdit.TopLine; RenamingFile:=False; - ChangedFileType:=''; // find the main declaration StartCaretXY:=StartSrcEdit.CursorTextXY; @@ -487,6 +488,7 @@ begin PascalReferences:=nil; ListOfLazFPDocNode:=nil; NewFilename:=''; + NewFileCreated:=false; OldRefs:=nil; try // let user choose the search scope @@ -500,40 +502,60 @@ begin Options:=MiscellaneousOptions.FindRenameIdentifierOptions; if Options.Rename then begin OldFileName:=DeclCodeXY.Code.Filename; - NewFilename:=OldFileName; if DeclNode.Desc=ctnSrcName then begin // rename unit/program - NewFilename:=ExtractFilePath(OldFileName)+ - LowerCase(RemoveAmpersands(Options.RenameTo))+ - ExtractFileExt(OldFileName); - RenamingFile:= CompareFileNames(ExtractFilenameOnly(NewFilename), - ExtractFilenameOnly(OldFileName))<>0; - if RenamingFile and FileExists(NewFilename) then begin - IDEMessageDialog(lisRenamingAborted, - Format(lisFileAlreadyExists,[NewFilename]), - mtError,[mbOK]); - exit(mrCancel); - end; - ChangedFileType:=lowercase(CodeToolBoss.GetSourceType(DeclCodeXY.Code,False)); - if ChangedFileType='' then - RenamingFile:=false - else begin - case ChangedFileType of - 'program': SrcNamed:=dlgFoldPasProgram; - 'library': SrcNamed:=lisPckOptsLibrary; - 'package': SrcNamed:=lisPackage; + DoLowercase:=false; + if Options.RenameTo<>lowercase(Options.RenameTo) then begin + // new identifier is not lowercase + case EnvironmentOptions.CharcaseFileAction of + ccfaAsk: + begin + Confirm:=true; + s:=ExtractFileName(OldFileName); + if (s=lowercase(s)) and (Identifier<>lowercase(Identifier)) then begin + // old unitname was mixed case and old file was lowercase -> keep policy, no need to ask + Confirm:=false; + end; + if Confirm then begin + s:=RemoveAmpersands(Options.RenameTo)+ExtractFileExt(OldFileName); + Result:=IDEQuestionDialog(lisFileNotLowercase, + Format(lisTheUnitIsNotLowercaseTheFreePascalCompiler, + [s, LineEnding, LineEnding+LineEnding]), + mtConfirmation,[mrYes,mrNo,mrCancel],''); + case Result of + mrYes: DoLowercase:=true; + mrNo: ; + else + exit(mrCancel); + end; + end; + end; + ccfaAutoRename: + // always lower case + DoLowercase:=true; else - SrcNamed:=dlgFoldPasUnit; + // use mixed case for filename end; end; - if RenamingFile then begin - if (IDEMessageDialog(srkmecRenameIdentifier, - Format(lisTheIdentifierIsAUnitProceedAnyway, - [SrcNamed,LineEnding,LineEnding]), - mtInformation,[mbCancel, mbOK],'') <> mrOK) - then - exit(mrCancel); + if DoLowercase then + NewFilename:=ExtractFilePath(OldFileName)+ + lowercase(RemoveAmpersands(Options.RenameTo)+ExtractFileExt(OldFileName)) + else + NewFilename:=ExtractFilePath(OldFileName)+ + RemoveAmpersands(Options.RenameTo)+ + ExtractFileExt(OldFileName); + + // Check if new file already exists (change in case is silently done) + MovingFile:=CompareFilenames(ExtractFilePath(OldFileName),ExtractFilePath(NewFilename))<>0; + RenamingFile:=MovingFile or (ExtractFileName(NewFilename)<>ExtractFileName(OldFileName)); + if (MovingFile or not SameText(ExtractFileName(NewFilename),ExtractFileName(OldFileName))) + and CodeToolBoss.DirectoryCachePool.FileExists(NewFilename,ctsfcAllCase) + then begin + IDEMessageDialog(lisRenamingAborted, + Format(lisFileAlreadyExists,[FindDiskFilename(NewFilename)]), + mtError,[mbOK]); + exit(mrCancel); end; end; end; @@ -639,7 +661,8 @@ begin if Options.Rename then begin - if RenamingFile then begin + if RenamingFile or (ExtractFileName(OldFileName)<>ExtractFilename(NewFilename)) + then begin // rename file, and associated lfm, res, etc, // keeping source editor and session data // rename source name in this file @@ -651,6 +674,7 @@ begin exit(mrCancel); DeclCodeXY.Code:=CodeToolBoss.LoadFile(NewFilename,false,false); + NewFileCreated:=true; end; // rename identifier @@ -722,7 +746,7 @@ begin PascalReferences.Free; FreeListObjects(ListOfLazFPDocNode,true); - if RenamingFile and (Result=mrOK) then + if RenamingFile and NewFileCreated then // source renamed -> jump to new file Result:=LazarusIDE.DoOpenFileAndJumpToPos(NewFilename, DeclXY, StartTopLine,-1,-1,[ofOnlyIfExists,ofRegularFile,ofDoNotLoadResource]) @@ -1511,6 +1535,7 @@ begin FNodesDeletedChangeStep:=FTool.NodesDeletedChangeStep; NewEdit.Text:=FOldIdentifier; end; + procedure TFindRenameIdentifierDialog.GatherFiles; var StartSrcEdit: TSourceEditorInterface; diff --git a/ide/main.pp b/ide/main.pp index f2415c9226..4d185fe28d 100644 --- a/ide/main.pp +++ b/ide/main.pp @@ -107,7 +107,7 @@ uses LazDebuggerGdbmi, GDBMIDebugger, RunParamsOpts, BaseDebugManager, DebugManager, debugger, DebuggerDlg, DebugAttachDialog, DbgIntfDebuggerBase, DbgIntfProcess, LazDebuggerIntf, LazDebuggerIntfBaseTypes, - idedebuggerpackage, FpDebugValueConvertors, IdeDebuggerBackendValueConv, IdeDebuggerBase, + idedebuggerpackage, FpDebugValueConvertors, IdeDebuggerBase, // packager PackageSystem, PkgManager, BasePkgManager, LPKCache, LazarusPackageIntf, PackageEditor, // source editing diff --git a/ide/packages/ideconfig/environmentopts.pp b/ide/packages/ideconfig/environmentopts.pp index 68fd226242..17dee61836 100644 --- a/ide/packages/ideconfig/environmentopts.pp +++ b/ide/packages/ideconfig/environmentopts.pp @@ -142,9 +142,9 @@ const type TCharCaseFileAction = ( - ccfaAsk, - ccfaAutoRename, - ccfaIgnore + ccfaAsk, // before saving as non lowercase, ask + ccfaAutoRename, // auto lowercase + ccfaIgnore // don't ask, save whatever case ); TCharCaseFileActions = set of TCharCaseFileAction; diff --git a/ide/packages/ideutils/dialogprocs.pas b/ide/packages/ideutils/dialogprocs.pas index 031da1a353..189e2c6c7e 100644 --- a/ide/packages/ideutils/dialogprocs.pas +++ b/ide/packages/ideutils/dialogprocs.pas @@ -110,7 +110,7 @@ function CreateSymlinkInteractive(const {%H-}LinkFilename, {%H-}TargetFilename: function ForceDirectoryInteractive(Directory: string; ErrorButtons: TMsgDlgButtons = []): TModalResult; function DeleteFileInteractive(const Filename: string; - ErrorButtons: TMsgDlgButtons = []): TModalResult; + ErrorButtons: TMsgDlgButtons = []; CaseInsensitive: boolean = false): TModalResult; function SaveLazStringToFile(const Filename, Content: string; ErrorButtons: TMsgDlgButtons; const Context: string = '' ): TModalResult; @@ -590,15 +590,39 @@ begin Result:=mrOk; end; -function DeleteFileInteractive(const Filename: string; - ErrorButtons: TMsgDlgButtons): TModalResult; +function DeleteFileInteractive(const Filename: string; ErrorButtons: TMsgDlgButtons; + CaseInsensitive: boolean): TModalResult; +var + Dir, ShortFilename: string; + Info: TSearchRec; + CurFilename: String; + Found: Boolean; begin + CurFilename:=Filename; repeat Result:=mrOk; - if not FileExistsUTF8(Filename) then exit; - if not DeleteFileUTF8(Filename) then begin + if CaseInsensitive then begin + Dir:=ExtractFilePath(Filename); + Found:=false; + if FindFirstUTF8(Dir+AllFilesMask,faAnyFile,Info)=0 then begin + ShortFilename:=ExtractFileName(Filename); + repeat + CurFilename:=Info.Name; + if (CurFilename='') or (CurFilename='.') or (CurFilename='..') then continue; + if SameText(Filename,ShortFilename) then begin + CurFilename:=Dir+CurFilename; + Found:=true; + break; + end; + until FindNextUTF8(Info)<>0; + end; + FindCloseUTF8(Info); + if not Found then exit; + end else if not FileExistsUTF8(Filename) then + exit; + if not DeleteFileUTF8(CurFilename) then begin Result:=LazMessageDialogAb(lisDeleteFileFailed, - Format(lisPkgMangUnableToDeleteFile, [Filename]), + Format(lisPkgMangUnableToDeleteFile, [CurFilename]), mtError,[mbCancel,mbRetry]+ErrorButtons-[mbAbort],mbAbort in ErrorButtons); if Result<>mrRetry then exit; end; diff --git a/ide/sourcefilemanager.pas b/ide/sourcefilemanager.pas index 8f8391fc2c..70edab6a59 100644 --- a/ide/sourcefilemanager.pas +++ b/ide/sourcefilemanager.pas @@ -2894,8 +2894,8 @@ begin then begin if EnvironmentOptions.UnitRenameReferencesAction<>urraNever then begin - // silently update references of new units (references were auto created - // and keeping old references makes no sense) + // silently update references of new(virtual) units, + // because references were auto created and keeping old references makes no sense Confirm:=(EnvironmentOptions.UnitRenameReferencesAction=urraAsk) and (not WasVirtual); Result:=ReplaceUnitUse(OldFilename,OldUnitName,NewFilename,NewUnitName, @@ -4908,6 +4908,7 @@ var Filter, AllEditorExt, AllFilter, AmpUnitname: string; r: integer; begin + if Flags=[] then ; if (AnUnitInfo<>nil) and (AnUnitInfo.OpenEditorInfoCount>0) then SrcEdit := TSourceEditor(AnUnitInfo.OpenEditorInfo[0].EditorComponent) else @@ -4931,16 +4932,13 @@ begin end else OldUnitName:=''; //debugln('ShowSaveFileAsDialog sourceunitname=',OldUnitName); - if sfskipReferences in Flags then - SaveAsFilename:=LowerCase(RemoveAmpersands(OldUnitName)) - else - SaveAsFilename:=RemoveAmpersands(OldUnitName); + SaveAsFilename:=RemoveAmpersands(OldUnitName); if SaveAsFilename='' then SaveAsFilename:=ExtractFileNameOnly(AFilename); if SaveAsFilename='' then SaveAsFilename:=lisnoname; - //suggest lowercased name if user wants so + // suggest lowercased name if user wants so if EnvironmentOptions.LowercaseDefaultFilename then SaveAsFilename:=LowerCase(SaveAsFilename); @@ -4983,9 +4981,6 @@ begin APath:=PkgBoss.GetDefaultSaveDirectoryForFile(AFilename); if (APath<>'') and (not PathIsInPath(SaveDialog.InitialDir,APath)) then SaveDialog.InitialDir:=APath; - if (sfSkipReferences in Flags) then begin - NewFileName:= ExtractFilePath(AFileName)+SaveAsFileName+SaveAsFileExt; - end else repeat Result:=mrCancel; // show save dialog @@ -5072,31 +5067,26 @@ begin // check filename if FilenameIsPascalUnit(NewFilename) then begin - if sfSkipReferences in Flags then begin - // F2 forced lowercase filename - // NewFileName already set - end else begin - AText:=ExtractFileName(NewFilename); - // check if file should be auto renamed - case EnvironmentOptions.CharcaseFileAction of - ccfaAsk: - if LowerCase(AText)<>AText then begin - Result:=IDEQuestionDialogAb(lisRenameFile, - Format(lisThisLooksLikeAPascalFileItIsRecommendedToUseLowerC, - [LineEnding, LineEnding]), - mtWarning, [mrYes, lisRenameToLowercase, - mrNo, lisKeepName, - mrAbort, lisAbort], not CanAbort); - case Result of - mrYes: NewFileName:=ExtractFilePath(NewFilename)+lowercase(AText); - mrAbort, mrCancel: exit; - end; - Result:=mrOk; + AText:=ExtractFileName(NewFilename); + // check if file should be auto renamed + case EnvironmentOptions.CharcaseFileAction of + ccfaAsk: + if LowerCase(AText)<>AText then begin + Result:=IDEQuestionDialogAb(lisRenameFile, + Format(lisThisLooksLikeAPascalFileItIsRecommendedToUseLowerC, + [LineEnding, LineEnding]), + mtWarning, [mrYes, lisRenameToLowercase, + mrNo, lisKeepName, + mrAbort, lisAbort], not CanAbort); + case Result of + mrYes: NewFileName:=ExtractFilePath(NewFilename)+lowercase(AText); + mrAbort, mrCancel: exit; end; - ccfaAutoRename: - NewFileName:=ExtractFilePath(NewFilename)+LowerCase(AText); - ccfaIgnore: ; + Result:=mrOk; end; + ccfaAutoRename: + NewFileName:=ExtractFilePath(NewFilename)+LowerCase(AText); + ccfaIgnore: ; end; end; @@ -5788,7 +5778,7 @@ var AmbiguousFiles: TStringList; i: Integer; DirRelation: TSPFileMaskRelation; - OldFileRemoved, Silence: Boolean; + OldFileRemoved, Silence, OnlyCaseChanged: Boolean; ConvTool: TConvDelphiCodeTool; AEditor: TSourceEditor; begin @@ -5803,6 +5793,11 @@ begin OldFilename:=AnUnitInfo.Filename; OldFilePath:=ExtractFilePath(OldFilename); OldLFMFilename:=''; + NewFilePath:=ExtractFilePath(NewFilename); + + OnlyCaseChanged:=(CompareFilenames(OldFilePath,NewFilePath)=0) + and SameText(ExtractFilename(OldFilename),ExtractFileName(NewFilename)); + // ToDo: use UnitResources if FilenameHasPascalExt(OldFilename) then begin OldLFMFilename:=ChangeFileExt(OldFilename,'.lfm'); @@ -5820,12 +5815,16 @@ begin if AnUnitInfo.ComponentName='' then begin // unit has no component // -> remove lfm file, so that it will not be auto loaded on next open - if (FileExistsUTF8(NewLFMFilename)) - and (not DeleteFileUTF8(NewLFMFilename)) - and (IDEMessageDialog(lisPkgMangDeleteFailed, - Format(lisDeletingOfFileFailed, [NewLFMFilename]), - mtError, [mbIgnore, mbCancel])=mrCancel) - then + if not (DeleteFileInteractive(NewLFMFilename,[mbIgnore],true) in [mrOk,mrIgnore]) then + exit(mrCancel); + end; + + if OnlyCaseChanged then begin + // remove old file + if not (DeleteFileInteractive(OldFilename,[mbIgnore],true) in [mrOk,mrIgnore]) then + exit(mrCancel); + if (OldLFMFilename<>'') + and not (DeleteFileInteractive(OldLFMFilename,[mbIgnore],true) in [mrOk,mrIgnore]) then exit(mrCancel); end; @@ -8237,38 +8236,26 @@ function ShowSaveProjectAsDialog(Flags: TSaveFlags=[]): TModalResult; var SaveDialog: TSaveDialog; NewProgramName: String; - NewPath, ANewPath, NewLPIFilename, NewProgramFN: String; + NewPath, NewLPIFilename, NewProgramFN: String; AFilename, Ext, AText, ACaption, OldProjectDir: string; begin + if Flags=[] then ; Project1.BeginUpdate(false); try OldProjectDir := Project1.Directory; // build a nice project info filename suggestion - if Assigned(Project1.MainUnitInfo) then begin + NewProgramName:=''; + if Assigned(Project1.MainUnitInfo) then NewProgramName := Project1.MainUnitInfo.ReadUnitNameFromSource(false); - ANewPath := ExtractFilePath(Project1.MainUnitInfo.Filename); - AFileName := RemoveAmpersands(NewProgramName); - end; - if AFilename = '' then begin - NewProgramName := ExtractFileName(Project1.ProjectInfoFile); - ANewPath := ExtractFilePath(Project1.ProjectInfoFile); - AFilename := RemoveAmpersands(NewProgramName); - end; - if AFilename = '' then begin + if NewProgramName = '' then + NewProgramName := ExtractFileNameOnly(Project1.ProjectInfoFile); + if NewProgramName = '' then NewProgramName := Trim(Project1.GetTitle); - ANewPath := Project1.Directory; - AFilename := RemoveAmpersands(NewProgramName); - end; - if AFilename = '' then begin + if NewProgramName = '' then NewProgramName := 'Project1'; - ANewPath := Project1.Directory; - AFilename := NewProgramName; - end; // Filename extension Ext := '.lpi'; - AFilename := AFilename + Ext; - if sfSkipReferences in Flags then - AFilename := LowerCase(AFilename); + AFilename := RemoveAmpersands(NewProgramName)+Ext; SaveDialog := IDESaveDialogClass.Create(nil); try @@ -8276,7 +8263,7 @@ begin SaveDialog.Title := Format(lisSaveProject, [Project1.GetTitleOrName, Ext]); // apply naming conventions, suggest lowercased name if user wants so - if EnvironmentOptions.LowercaseDefaultFilename or (sfSkipReferences in Flags) then + if EnvironmentOptions.LowercaseDefaultFilename then SaveDialog.FileName := LowerCase(AFilename) else SaveDialog.FileName := AFilename; @@ -8291,19 +8278,22 @@ begin Result:=mrCancel; NewLPIFilename:=''; // the project info file name NewProgramFN:=''; // the program source filename - if not (sfSkipReferences in Flags) and not SaveDialog.Execute then + if not SaveDialog.Execute then exit; // user cancels - if not (sfSkipReferences in Flags) then - AFilename := ExpandFileNameUTF8(SaveDialog.FileName); + AFilename := ExpandFileNameUTF8(SaveDialog.FileName); + // Note: the user might have chosen a filename without proper extension, e.g. Foo.Bar // check program name - if not (sfSkipReferences in Flags) then + if FilenameIsPascalSource(AFilename) or (CompareFileExt(AFilename,Ext)=0) then + begin NewProgramName:=ExtractFileNameOnly(AFilename); + NewLPIFilename:=ChangeFileExt(AFilename,Ext); + end else begin + // no extension. Note: could be dotted name like Foo.Bar + NewProgramName:=ExtractFileName(AFilename); + NewLPIFilename:=AFilename+Ext; + end; if (NewProgramName='') then begin - if (sfSkipReferences in Flags) then begin - Result:=mrAbort; - exit; - end; Result:=IDEMessageDialog(lisInvalidProjectFilename, Format(lisisAnInvalidProjectNamePleaseChooseAnotherEGProject,[SaveDialog.Filename,LineEnding]), mtInformation,[mbRetry,mbIgnore,mbAbort]); @@ -8313,13 +8303,6 @@ begin if not IsValidDottedIdent(NewProgramName) then NewProgramName:=ExtractPasIdentifier(NewProgramName,true); - if (sfSkipReferences in Flags) then - NewPath :=ANewPath - else - NewPath := ExtractFilePath(AFilename); - // append default extension - NewLPIFilename:=NewPath+ExtractFileNameOnly(AFilename)+'.lpi'; - if Project1.MainUnitID >= 0 then begin // check mainunit filename