internal static void ApplyChangesToOneFile( FileChange file, IVsTextLines textLines, bool createMarker, ChangeProposal changeToHighlight) { ArgumentValidation.CheckForNullReference(file, "file"); ArgumentValidation.CheckForNullReference(textLines, "textLines"); // Here we only work on text based ChangeProposal // Sort all ChangeProposal in one file by the offset. // After apply any change, the offset of next ChangeProposal will change. // We need to sort all the changes first, so it will be easy to recalculate // the new offset for next change. var sortedChanges = SortChanges(file); // Changes may cause the total line number of this file to be changed. // This variable is used to keep this line changes. var totalLineOffset = 0; // In one line, changes may cause column number of that line to be changed. // This variable is used to keep this column changes. var totalColumnOffset = 0; var pContent = IntPtr.Zero; // Store result TextSpan info var resultSpan = new[] { new TextSpan() }; foreach (var sortedChange in sortedChanges) { var change = sortedChange.Value; // If user choose to apply this change if (change.Included) { try { // If current change's StartLine is different from Last change's EndLine // reset the columnOffset if (resultSpan[0].iEndLine != change.StartLine + totalLineOffset) { totalColumnOffset = 0; } pContent = Marshal.StringToHGlobalAuto(change.NewValue); // Calculate the current change's offset by applying lineOffset // and columnOffset caused by previous changes var startLine = change.StartLine + totalLineOffset; var startColumn = change.StartColumn + totalColumnOffset; var endLine = change.EndLine + totalLineOffset; // Only apply columnOffset if current change's startLine is same as endLine var endColumn = change.EndColumn + (startLine == endLine ? totalColumnOffset : 0); textLines.ReplaceLines( startLine, startColumn, endLine, endColumn, pContent, change.NewValue.Length, resultSpan); // Add the line offset caused by this change to total lineOffset; var currentLineOffset = resultSpan[0].iEndLine - endLine; totalLineOffset += currentLineOffset; // Calculate the ColumnOffset after this change totalColumnOffset = resultSpan[0].iEndIndex - change.EndColumn; } finally { if (pContent != IntPtr.Zero) { Marshal.FreeHGlobal(pContent); } } } // Create Marker for this change's textspan. // If this change is the change to be highlighted, highlight it. if (createMarker) { var textChangeToHighlight = changeToHighlight as TextChangeProposal; var highlight = ((textChangeToHighlight != null) && (change.StartLine == textChangeToHighlight.StartLine) && (change.StartColumn == textChangeToHighlight.StartColumn)); CreateMarker(textLines, resultSpan[0], highlight); } } }
/// <summary> /// Display the refactoring preview for a selected preview changes node. /// </summary> /// <param name="vsTextView">The text view to show the file contents.</param> /// <param name="fileChange">All changes in one file.</param> /// <param name="node">The selected PreviewChangesNode.</param> public void DisplayPreview(IVsTextView vsTextView, FileChange fileChange, PreviewChangesNode node) { ArgumentValidation.CheckForNullReference(vsTextView, "vsTextView"); ArgumentValidation.CheckForNullReference(node, "previewChangeNode"); if (fileChange != null) { // Get the temp file for this file extension, and copy all file content to the temp file text buffer PreviewTempFile tempFile = null; try { tempFile = GetPreviewTempFile(Path.GetExtension(fileChange.FileName)); } catch (InvalidOperationException) { // Failed to get text buffer, just set the text view to nothing. NativeMethods.ThrowOnFailure(vsTextView.SetBuffer(_bufferForNoChanges)); return; } // Copy the content of source file to that temp file text buffer CopyFileToBuffer(fileChange.FileName, tempFile.TextBuffer); // Create text markers on all changes on this file RefactoringOperationBase.ApplyChangesToOneFile(fileChange, tempFile.TextBuffer, true, node.ChangeProposal); // Set language service ID // Get Language service ID on this change node var languageServiceID = node.LanguageServiceID; if (languageServiceID == Guid.Empty && node.ChildList != null && node.ChildList.Count > 0) { // If can not get the language service ID, check if it has child nodes // if so, get the language service ID for first change in this file node. languageServiceID = node.ChildList[0].LanguageServiceID; } if (languageServiceID != Guid.Empty) { NativeMethods.ThrowOnFailure(tempFile.TextBuffer.SetLanguageServiceID(ref languageServiceID)); } // Set the vsTextView with textBuffer NativeMethods.ThrowOnFailure(vsTextView.SetBuffer(tempFile.TextBuffer)); // Ensure visible of first line and set the caret to position (0,0) ScrollInView(vsTextView, 0, 0, 0, 1); // If there is ChangeProposal, make sure that change is visible in the text view. // If ChangeProposal is null, that might be file node, make the first chagne visible. // Here we will only work with Text based change proposal. var visibleChange = node.ChangeProposal as TextChangeProposal; if (visibleChange == null) { // Try to get first change in this file if (node.ChildList != null && node.ChildList.Count > 0) { visibleChange = node.ChildList[0].ChangeProposal as TextChangeProposal; } } if (visibleChange != null) { // There are some changes, create TextSpan for first change, // and make the cursor position to that TextSpan. ScrollInView( vsTextView, visibleChange.StartLine, visibleChange.StartColumn, visibleChange.EndLine, visibleChange.EndColumn); } // Save the state, this will be used to refresh the text view when preview request // changed, such as check/uncheck _lastTextView = vsTextView; _lastDisplayedFileChange = fileChange; _lastDisplayedNode = node; } else { // No related file to this node, set nothing for the text view. NativeMethods.ThrowOnFailure(vsTextView.SetBuffer(_bufferForNoChanges)); } }
protected override IList<FileChange> GetFileChanges() { var fileChangeMap = new Dictionary<string, FileChange>(); using (WaitCursorHelper.NewWaitCursor()) { var artifact = _contributorInput.ObjectToBeRenamed.Artifact; var artifactProjectItem = VsUtils.GetProjectItemForDocument(artifact.Uri.LocalPath, Services.ServiceProvider); if (artifactProjectItem != null) { // Run the custom tool to ensure the generated code is up-to-date VsUtils.RunCustomTool(artifactProjectItem); var generatedCodeProjectItem = VsUtils.GetGeneratedCodeProjectItem(artifactProjectItem); if (generatedCodeProjectItem != null) { var generatedItemPath = generatedCodeProjectItem.get_FileNames(1); var objectSearchLanguage = FileExtensions.VbExt.Equals( Path.GetExtension(generatedItemPath), StringComparison.OrdinalIgnoreCase) ? ObjectSearchLanguage.VB : ObjectSearchLanguage.CSharp; var codeElements = generatedCodeProjectItem.FileCodeModel.CodeElements.OfType<CodeElement2>(); CodeElementRenameData renameData = null; // Check if we're refactoring an EntityType or Property var entityType = _contributorInput.ObjectToBeRenamed as EntityType; var property = _contributorInput.ObjectToBeRenamed as Property; if (entityType != null) { var newEntitySetName = _contributorInput.NewName; var pluralize = ModelHelper.GetDesignerPropertyValueFromArtifactAsBool( OptionsDesignerInfo.ElementName, OptionsDesignerInfo.AttributeEnablePluralization, OptionsDesignerInfo.EnablePluralizationDefault, artifact); // Pluralize the entity set name if the setting it turned on if (pluralize) { var pluralizationService = DependencyResolver.GetService<IPluralizationService>(); newEntitySetName = pluralizationService.Pluralize(_contributorInput.NewName); } renameData = new CodeElementRenameData( _contributorInput.NewName, newEntitySetName, _contributorInput.OldName, entityType.EntitySet.Name.Value); } else if (property != null) { if (property.EntityType != null) { renameData = new CodeElementRenameData( _contributorInput.NewName, _contributorInput.OldName, property.EntityType.Name.Value); } } if (renameData != null) { var codeElementsToRename = new Dictionary<CodeElement2, Tuple<string, string>>(); CodeElementUtilities.FindRootCodeElementsToRename( codeElements, renameData, generatedItemPath, objectSearchLanguage, ref codeElementsToRename); var changeProposals = new List<ChangeProposal>(); // We may need to rename more than one object, as type names affect functions and entity set properties. This means we need to loop through and // process each root item change in the generated code designer file. foreach (var codeElementToRename in codeElementsToRename.Keys) { var nameTuple = codeElementsToRename[codeElementToRename]; CodeElementUtilities.CreateChangeProposals( codeElementToRename, nameTuple.Item1, nameTuple.Item2, generatedItemPath, changeProposals, objectSearchLanguage); } // Now sort the change proposals by filename so that we can return a list of file changes foreach (var changeProposal in changeProposals) { FileChange fileChange; HashSet<ChangeProposal> fileChangeProposals; if (fileChangeMap.TryGetValue(changeProposal.FileName, out fileChange)) { fileChangeProposals = fileChange.ChangeList.Values.First(); } else { fileChange = new FileChange(changeProposal.FileName); fileChangeProposals = new HashSet<ChangeProposal>(); fileChange.ChangeList.Add( new KeyValuePair<RefactoringPreviewGroup, HashSet<ChangeProposal>>( new RefactoringPreviewGroup(Resources.RefactorPreviewGroupName), fileChangeProposals)); fileChangeMap.Add(changeProposal.FileName, fileChange); } if (!fileChangeProposals.Contains(changeProposal)) { fileChangeProposals.Add(changeProposal); } } } } } } return fileChangeMap.Values.ToList(); }