Browse Source

New feature: move class to file

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@1583 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
shortcuts
Daniel Grunwald 19 years ago
parent
commit
3c18394e4b
  1. 23
      samples/NRefactoryDemo/MainForm.Designer.cs
  2. 4
      src/Libraries/NRefactory/Project/Src/Lexer/CSharp/Lexer.cs
  3. 4
      src/Main/Base/Project/Src/Dom/LanguageProperties.cs
  4. 238
      src/Main/Base/Project/Src/Services/RefactoringService/NRefactoryRefactoringProvider.cs
  5. 31
      src/Main/Base/Project/Src/Services/RefactoringService/RefactoringProvider.cs
  6. 108
      src/Main/Base/Project/Src/TextEditor/Commands/ClassBookmarkMenuBuilder.cs

23
samples/NRefactoryDemo/MainForm.Designer.cs generated

@ -40,6 +40,7 @@ namespace NRefactoryDemo @@ -40,6 +40,7 @@ namespace NRefactoryDemo
this.groupBox2 = new System.Windows.Forms.GroupBox();
this.transformationComboBox = new System.Windows.Forms.ComboBox();
this.applyTransformation = new System.Windows.Forms.Button();
this.editNodeButton = new System.Windows.Forms.Button();
this.deleteSelectedNode = new System.Windows.Forms.Button();
this.panel1 = new System.Windows.Forms.Panel();
this.arrowUpPictureBox = new System.Windows.Forms.PictureBox();
@ -51,7 +52,6 @@ namespace NRefactoryDemo @@ -51,7 +52,6 @@ namespace NRefactoryDemo
this.parseVBButton = new System.Windows.Forms.Button();
this.generateCSharpButton = new System.Windows.Forms.Button();
this.parseCSharpButton = new System.Windows.Forms.Button();
this.editNodeButton = new System.Windows.Forms.Button();
this.splitContainer1.Panel1.SuspendLayout();
this.splitContainer1.Panel2.SuspendLayout();
this.splitContainer1.SuspendLayout();
@ -90,6 +90,7 @@ namespace NRefactoryDemo @@ -90,6 +90,7 @@ namespace NRefactoryDemo
this.codeTextBox.Location = new System.Drawing.Point(0, 0);
this.codeTextBox.Multiline = true;
this.codeTextBox.Name = "codeTextBox";
this.codeTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.codeTextBox.Size = new System.Drawing.Size(512, 146);
this.codeTextBox.TabIndex = 0;
this.codeTextBox.Text = "using System;\r\nclass MainClass\r\n{\r\n // This is the entry method of the applicati" +
@ -143,6 +144,16 @@ namespace NRefactoryDemo @@ -143,6 +144,16 @@ namespace NRefactoryDemo
this.applyTransformation.UseVisualStyleBackColor = true;
this.applyTransformation.Click += new System.EventHandler(this.ApplyTransformationClick);
//
// editNodeButton
//
this.editNodeButton.Location = new System.Drawing.Point(9, 49);
this.editNodeButton.Name = "editNodeButton";
this.editNodeButton.Size = new System.Drawing.Size(110, 23);
this.editNodeButton.TabIndex = 1;
this.editNodeButton.Text = "Edit node";
this.editNodeButton.UseVisualStyleBackColor = true;
this.editNodeButton.Click += new System.EventHandler(this.EditNodeButtonClick);
//
// deleteSelectedNode
//
this.deleteSelectedNode.Location = new System.Drawing.Point(9, 20);
@ -254,16 +265,6 @@ namespace NRefactoryDemo @@ -254,16 +265,6 @@ namespace NRefactoryDemo
this.parseCSharpButton.UseVisualStyleBackColor = true;
this.parseCSharpButton.Click += new System.EventHandler(this.ParseCSharpButtonClick);
//
// editNodeButton
//
this.editNodeButton.Location = new System.Drawing.Point(9, 49);
this.editNodeButton.Name = "editNodeButton";
this.editNodeButton.Size = new System.Drawing.Size(110, 23);
this.editNodeButton.TabIndex = 1;
this.editNodeButton.Text = "Edit node";
this.editNodeButton.UseVisualStyleBackColor = true;
this.editNodeButton.Click += new System.EventHandler(this.EditNodeButtonClick);
//
// MainForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);

4
src/Libraries/NRefactory/Project/Src/Lexer/CSharp/Lexer.cs

@ -757,6 +757,8 @@ namespace ICSharpCode.NRefactory.Parser.CSharp @@ -757,6 +757,8 @@ namespace ICSharpCode.NRefactory.Parser.CSharp
if (ch == '*' && ReaderPeek() == '/') {
ReaderRead();
return;
} else {
HandleLineEnd(ch);
}
}
} else {
@ -765,7 +767,7 @@ namespace ICSharpCode.NRefactory.Parser.CSharp @@ -765,7 +767,7 @@ namespace ICSharpCode.NRefactory.Parser.CSharp
char ch = (char)nextChar;
if (HandleLineEnd(ch)) {
specialTracker.AddChar('\n');
specialTracker.AddString(Environment.NewLine);
continue;
}

4
src/Main/Base/Project/Src/Dom/LanguageProperties.cs

@ -162,7 +162,7 @@ namespace ICSharpCode.SharpDevelop.Dom @@ -162,7 +162,7 @@ namespace ICSharpCode.SharpDevelop.Dom
public override RefactoringProvider RefactoringProvider {
get {
return NRefactoryRefactoringProvider.NRefactoryProviderInstance;
return NRefactoryRefactoringProvider.NRefactoryCSharpProviderInstance;
}
}
@ -244,7 +244,7 @@ namespace ICSharpCode.SharpDevelop.Dom @@ -244,7 +244,7 @@ namespace ICSharpCode.SharpDevelop.Dom
public override RefactoringProvider RefactoringProvider {
get {
return NRefactoryRefactoringProvider.NRefactoryProviderInstance;
return NRefactoryRefactoringProvider.NRefactoryVBNetProviderInstance;
}
}

238
src/Main/Base/Project/Src/Services/RefactoringService/NRefactoryRefactoringProvider.cs

@ -8,8 +8,11 @@ @@ -8,8 +8,11 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using ICSharpCode.Core;
using ICSharpCode.NRefactory.Parser.AST;
using ICSharpCode.NRefactory.PrettyPrinter;
using ICSharpCode.SharpDevelop.Dom;
using NR = ICSharpCode.NRefactory.Parser;
@ -17,19 +20,45 @@ namespace ICSharpCode.SharpDevelop.Refactoring @@ -17,19 +20,45 @@ namespace ICSharpCode.SharpDevelop.Refactoring
{
public class NRefactoryRefactoringProvider : RefactoringProvider
{
public static readonly NRefactoryRefactoringProvider NRefactoryProviderInstance = new NRefactoryRefactoringProvider();
public static readonly NRefactoryRefactoringProvider NRefactoryCSharpProviderInstance = new NRefactoryRefactoringProvider(NR.SupportedLanguage.CSharp);
public static readonly NRefactoryRefactoringProvider NRefactoryVBNetProviderInstance = new NRefactoryRefactoringProvider(NR.SupportedLanguage.VBNet);
NR.SupportedLanguage language;
private NRefactoryRefactoringProvider(NR.SupportedLanguage language)
{
this.language = language;
}
public override bool IsEnabledForFile(string fileName)
{
string extension = Path.GetExtension(fileName);
if (extension.Equals(".cs", StringComparison.InvariantCultureIgnoreCase))
return true;
return language == NR.SupportedLanguage.CSharp;
else if (extension.Equals(".vb", StringComparison.InvariantCultureIgnoreCase))
return true;
return language == NR.SupportedLanguage.VBNet;
else
return false;
}
static void ShowSourceCodeErrors(string errors)
{
MessageService.ShowMessage("The operation cannot be performed because your source code contains errors:\n" + errors);
}
NR.IParser ParseFile(string fileContent)
{
NR.IParser parser = NR.ParserFactory.CreateParser(language, new StringReader(fileContent));
parser.Parse();
if (parser.Errors.count > 0) {
ShowSourceCodeErrors(parser.Errors.ErrorOutput);
parser.Dispose();
return null;
} else {
return parser;
}
}
#region FindUnusedUsingDeclarations
protected class PossibleTypeReference
{
@ -89,20 +118,13 @@ namespace ICSharpCode.SharpDevelop.Refactoring @@ -89,20 +118,13 @@ namespace ICSharpCode.SharpDevelop.Refactoring
protected virtual Dictionary<PossibleTypeReference, object> FindPossibleTypeReferences(string extension, string fileContent)
{
NR.IParser parser;
if (extension.Equals(".cs", StringComparison.InvariantCultureIgnoreCase))
parser = NR.ParserFactory.CreateParser(NR.SupportedLanguage.CSharp, new StringReader(fileContent));
else if (extension.Equals(".vb", StringComparison.InvariantCultureIgnoreCase))
parser = NR.ParserFactory.CreateParser(NR.SupportedLanguage.VBNet, new StringReader(fileContent));
else
return null;
parser.Parse();
if (parser.Errors.count > 0) {
MessageService.ShowMessage("The operation cannot be performed because your sourcecode contains errors:\n" + parser.Errors.ErrorOutput);
NR.IParser parser = ParseFile(fileContent);
if (parser == null) {
return null;
} else {
FindPossibleTypeReferencesVisitor visitor = new FindPossibleTypeReferencesVisitor();
parser.CompilationUnit.AcceptVisitor(visitor, null);
parser.Dispose();
return visitor.list;
}
}
@ -145,5 +167,195 @@ namespace ICSharpCode.SharpDevelop.Refactoring @@ -145,5 +167,195 @@ namespace ICSharpCode.SharpDevelop.Refactoring
return list;
}
#endregion
#region CreateNewFileLikeExisting
public override bool SupportsCreateNewFileLikeExisting {
get {
return true;
}
}
public override string CreateNewFileLikeExisting(string existingFileContent, string codeForNewType)
{
NR.IParser parser = ParseFile(existingFileContent);
if (parser == null) {
return null;
}
RemoveTypesVisitor visitor = new RemoveTypesVisitor();
parser.CompilationUnit.AcceptVisitor(visitor, null);
List<NR.ISpecial> comments = new List<NR.ISpecial>();
foreach (NR.ISpecial c in parser.Lexer.SpecialTracker.CurrentSpecials) {
if (c.StartPosition.Y <= visitor.includeCommentsUpToLine
|| c.StartPosition.Y > visitor.includeCommentsAfterLine)
{
comments.Add(c);
}
}
IOutputASTVisitor outputVisitor = (language==NR.SupportedLanguage.CSharp) ? new CSharpOutputVisitor() : (IOutputASTVisitor)new VBNetOutputVisitor();
using (SpecialNodesInserter.Install(comments, outputVisitor)) {
parser.CompilationUnit.AcceptVisitor(outputVisitor, null);
}
string expectedText;
if (language==NR.SupportedLanguage.CSharp)
expectedText = "using " + RemoveTypesVisitor.DummyIdentifier + ";";
else
expectedText = "Imports " + RemoveTypesVisitor.DummyIdentifier;
using (StringWriter w = new StringWriter()) {
using (StringReader r1 = new StringReader(outputVisitor.Text)) {
string line;
while ((line = r1.ReadLine()) != null) {
string trimLine = line.TrimStart();
if (trimLine == expectedText) {
string indentation = line.Substring(0, line.Length - trimLine.Length);
using (StringReader r2 = new StringReader(codeForNewType)) {
while ((line = r2.ReadLine()) != null) {
w.Write(indentation);
w.WriteLine(line);
}
}
} else {
w.WriteLine(line);
}
}
}
if (visitor.firstType) {
w.WriteLine(codeForNewType);
}
return w.ToString();
}
}
private class RemoveTypesVisitor : NR.AbstractAstTransformer
{
internal const string DummyIdentifier = "DummyNamespace!InsertionPos";
internal int includeCommentsUpToLine;
internal int includeCommentsAfterLine = int.MaxValue;
internal bool firstType = true;
public override object Visit(UsingDeclaration usingDeclaration, object data)
{
if (firstType) {
includeCommentsUpToLine = usingDeclaration.EndLocation.Y;
}
return null;
}
public override object Visit(NamespaceDeclaration namespaceDeclaration, object data)
{
includeCommentsAfterLine = namespaceDeclaration.EndLocation.Y;
if (firstType) {
includeCommentsUpToLine = namespaceDeclaration.StartLocation.Y;
return base.Visit(namespaceDeclaration, data);
} else {
RemoveCurrentNode();
return null;
}
}
public override object Visit(TypeDeclaration typeDeclaration, object data)
{
if (typeDeclaration.EndLocation.Y > includeCommentsAfterLine)
includeCommentsAfterLine = typeDeclaration.EndLocation.Y;
if (firstType) {
firstType = false;
ReplaceCurrentNode(new UsingDeclaration(DummyIdentifier));
} else {
RemoveCurrentNode();
}
return null;
}
}
#endregion
#region ExtractCodeForType
public override bool SupportsGetFullCodeRangeForType {
get {
return true;
}
}
public override DomRegion GetFullCodeRangeForType(string fileContent, IClass type)
{
NR.ILexer lexer = NR.ParserFactory.CreateLexer(language, new StringReader(fileContent));
// use the lexer to determine last token position before type start
// and next token position after type end
Stack<NR.Location> stack = new Stack<NR.Location>();
NR.Location lastPos = NR.Location.Empty;
NR.Token t = lexer.NextToken();
bool csharp = language == NR.SupportedLanguage.CSharp;
int eof = csharp ? NR.CSharp.Tokens.EOF : NR.VB.Tokens.EOF;
int attribStart = csharp ? NR.CSharp.Tokens.OpenSquareBracket : NR.VB.Tokens.LessThan;
int attribEnd = csharp ? NR.CSharp.Tokens.CloseSquareBracket : NR.VB.Tokens.GreaterThan;
while (t.kind != eof) {
if (t.kind == attribStart)
stack.Push(lastPos);
if (t.EndLocation.Y >= type.Region.BeginLine)
break;
lastPos = t.EndLocation;
if (t.kind == attribEnd && stack.Count > 0)
lastPos = stack.Pop();
t = lexer.NextToken();
}
stack = null;
// Skip until end of type
while (t.kind != eof) {
if (t.EndLocation.Y > type.BodyRegion.EndLine)
break;
t = lexer.NextToken();
}
int lastLineBefore = lastPos.IsEmpty ? 0 : lastPos.Y;
int firstLineAfter = t.EndLocation.IsEmpty ? int.MaxValue : t.EndLocation.Y;
lexer.Dispose(); lexer = null;
StringReader myReader = new StringReader(fileContent);
string line;
string mainLine;
int resultBeginLine = lastLineBefore + 1;
int resultEndLine = firstLineAfter - 1;
int lineNumber = 0;
int largestEmptyLineCount = 0;
int emptyLinesInRow = 0;
while ((line = myReader.ReadLine()) != null) {
lineNumber++;
if (lineNumber <= lastLineBefore)
continue;
if (lineNumber < type.Region.BeginLine) {
string trimLine = line.TrimStart();
if (trimLine.Length == 0) {
if (++emptyLinesInRow > largestEmptyLineCount) {
resultBeginLine = lineNumber;
}
}
} else if (lineNumber == type.Region.BeginLine) {
mainLine = line;
} else if (lineNumber == type.BodyRegion.EndLine) {
largestEmptyLineCount = 0;
emptyLinesInRow = 0;
resultEndLine = lineNumber;
} else if (lineNumber > type.BodyRegion.EndLine) {
if (lineNumber >= firstLineAfter)
break;
string trimLine = line.TrimStart();
if (trimLine.Length == 0) {
if (++emptyLinesInRow > largestEmptyLineCount) {
emptyLinesInRow = largestEmptyLineCount;
resultEndLine = lineNumber - emptyLinesInRow;
}
}
}
}
myReader.Dispose();
return new DomRegion(resultBeginLine, 0, resultEndLine, int.MaxValue);
}
#endregion
}
}

31
src/Main/Base/Project/Src/Services/RefactoringService/RefactoringProvider.cs

@ -40,5 +40,36 @@ namespace ICSharpCode.SharpDevelop.Refactoring @@ -40,5 +40,36 @@ namespace ICSharpCode.SharpDevelop.Refactoring
{
throw new NotSupportedException();
}
public virtual bool SupportsCreateNewFileLikeExisting {
get {
return false;
}
}
/// <summary>
/// Creates a new file that uses same header, usings and namespace like an existing file.
/// </summary>
/// <returns>the content for the new file,
/// or null if an error occurred (error will be displayed to the user)</returns>
/// <param name="existingFileContent">Content of the exisiting file</param>
/// <param name="codeForNewType">Code to put in the new file.</param>
public virtual string CreateNewFileLikeExisting(string existingFileContent, string codeForNewType)
{
throw new NotSupportedException();
}
public virtual bool SupportsGetFullCodeRangeForType {
get {
return false;
}
}
public virtual DomRegion GetFullCodeRangeForType(string fileContent, IClass type)
{
throw new NotSupportedException();
}
}
}

108
src/Main/Base/Project/Src/TextEditor/Commands/ClassBookmarkMenuBuilder.cs

@ -8,19 +8,20 @@ @@ -8,19 +8,20 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Text;
using System.Windows.Forms;
using ICSharpCode.Core;
using ICSharpCode.TextEditor;
using ICSharpCode.TextEditor.Document;
using ICSharpCode.SharpDevelop.Bookmarks;
using ICSharpCode.SharpDevelop.DefaultEditor.Gui.Editor;
using ICSharpCode.SharpDevelop.Dom;
using ICSharpCode.SharpDevelop.Bookmarks;
using ICSharpCode.SharpDevelop.Gui;
using ICSharpCode.SharpDevelop.Gui.ClassBrowser;
using SearchAndReplace;
using ICSharpCode.SharpDevelop.DefaultEditor.Commands;
using ICSharpCode.SharpDevelop.Project;
using ICSharpCode.SharpDevelop.Refactoring;
using ICSharpCode.TextEditor.Document;
using SearchAndReplace;
namespace ICSharpCode.SharpDevelop.DefaultEditor.Commands
{
@ -41,9 +42,38 @@ namespace ICSharpCode.SharpDevelop.DefaultEditor.Commands @@ -41,9 +42,38 @@ namespace ICSharpCode.SharpDevelop.DefaultEditor.Commands
c = bookmark.Class;
}
LanguageProperties language = c.ProjectContent.Language;
List<ToolStripItem> list = new List<ToolStripItem>();
if (!FindReferencesAndRenameHelper.IsReadOnly(c)) {
if (!c.Name.Equals(Path.GetFileNameWithoutExtension(c.CompilationUnit.FileName),
StringComparison.InvariantCultureIgnoreCase))
{
// File name does not match class name
string correctFileName = Path.Combine(Path.GetDirectoryName(c.CompilationUnit.FileName),
c.Name + Path.GetExtension(c.CompilationUnit.FileName));
if (FileUtility.IsValidFileName(correctFileName)
&& !File.Exists(correctFileName))
{
if (c.CompilationUnit.Classes.Count == 1) {
// Rename file to ##
cmd = new MenuCommand("Rename file to " + Path.GetFileName(correctFileName),
delegate {
FileService.RenameFile(c.CompilationUnit.FileName, correctFileName, false);
});
list.Add(cmd);
} else if (language.RefactoringProvider.SupportsCreateNewFileLikeExisting && language.RefactoringProvider.SupportsGetFullCodeRangeForType) {
// Move class to file ##
cmd = new MenuCommand("Move class to file " + Path.GetFileName(correctFileName),
delegate {
MoveClassToFile(c, correctFileName);
});
list.Add(cmd);
}
}
}
cmd = new MenuCommand("${res:SharpDevelop.Refactoring.RenameCommand}", Rename);
cmd.Tag = c;
list.Add(cmd);
@ -70,6 +100,74 @@ namespace ICSharpCode.SharpDevelop.DefaultEditor.Commands @@ -70,6 +100,74 @@ namespace ICSharpCode.SharpDevelop.DefaultEditor.Commands
return list.ToArray();
}
static void MoveClassToFile(IClass c, string newFileName)
{
LanguageProperties language = c.ProjectContent.Language;
string existingCode = ParserService.GetParseableFileContent(c.CompilationUnit.FileName);
DomRegion fullRegion = language.RefactoringProvider.GetFullCodeRangeForType(existingCode, c);
if (fullRegion.IsEmpty) return;
string newCode = ExtractCode(c, fullRegion, c.BodyRegion.BeginLine);
newCode = language.RefactoringProvider.CreateNewFileLikeExisting(existingCode, newCode);
IWorkbenchWindow window = FileService.NewFile(newFileName, "Text", newCode);
window.ViewContent.Save(newFileName);
IProject project = c.ProjectContent.Project;
if (project != null) {
FileProjectItem projectItem = new FileProjectItem(project, ItemType.Compile);
projectItem.FileName = newFileName;
ProjectService.AddProjectItem(project, projectItem);
project.Save();
ProjectBrowserPad.Instance.ProjectBrowserControl.RefreshView();
}
}
static string ExtractCode(IClass c, DomRegion codeRegion, int indentationLine)
{
IDocument doc = GetDocument(c);
if (indentationLine < 0) indentationLine = 0;
if (indentationLine >= doc.TotalNumberOfLines) indentationLine = doc.TotalNumberOfLines - 1;
LineSegment segment = doc.GetLineSegment(indentationLine - 1);
string mainLine = doc.GetText(segment);
string indentation = mainLine.Substring(0, mainLine.Length - mainLine.TrimStart().Length);
segment = doc.GetLineSegment(codeRegion.BeginLine - 1);
int startOffset = segment.Offset;
segment = doc.GetLineSegment(codeRegion.EndLine - 1);
int endOffset = segment.Offset + segment.Length;
StringReader reader = new StringReader(doc.GetText(startOffset, endOffset - startOffset));
doc.Remove(startOffset, endOffset - startOffset);
doc.RequestUpdate(new ICSharpCode.TextEditor.TextAreaUpdate(ICSharpCode.TextEditor.TextAreaUpdateType.WholeTextArea));
doc.CommitUpdate();
// now remove indentation from extracted source code
string line;
StringBuilder b = new StringBuilder();
int endOfLastFilledLine = 0;
while ((line = reader.ReadLine()) != null) {
int startpos;
for (startpos = 0; startpos < line.Length && startpos < indentation.Length; startpos++) {
if (line[startpos] != indentation[startpos])
break;
}
if (startpos == line.Length) {
// empty line
if (b.Length > 0) {
b.AppendLine();
}
} else {
b.Append(line, startpos, line.Length - startpos);
b.AppendLine();
endOfLastFilledLine = b.Length;
}
}
b.Length = endOfLastFilledLine;
return b.ToString();
}
void AddImplementInterfaceCommandItems(List<ToolStripItem> subItems, IClass c, bool explicitImpl, ModifierEnum modifier)
{
CodeGenerator codeGen = c.ProjectContent.Language.CodeGenerator;

Loading…
Cancel
Save