diff --git a/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj b/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj
index 37925b3369..faf1a48e07 100644
--- a/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj
+++ b/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj
@@ -503,6 +503,7 @@
+
@@ -534,4 +535,4 @@
-
+
diff --git a/ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/SortUsingsAction.cs b/ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/SortUsingsAction.cs
new file mode 100644
index 0000000000..6fa08d4e2a
--- /dev/null
+++ b/ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/SortUsingsAction.cs
@@ -0,0 +1,145 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using ICSharpCode.NRefactory.Semantics;
+using ICSharpCode.NRefactory.TypeSystem;
+using ICSharpCode.NRefactory.TypeSystem.Implementation;
+
+namespace ICSharpCode.NRefactory.CSharp.Refactoring.CodeActions
+{
+ [ContextAction("Sort usings", Description = "Sorts usings by their origin and then alphabetically.")]
+ public class SortUsingsAction: ICodeActionProvider
+ {
+ public IEnumerable GetActions(RefactoringContext context)
+ {
+ var usingNode = FindUsingNodeAtCursor(context);
+ if (usingNode == null)
+ yield break;
+
+ yield return new CodeAction(context.TranslateString("Sort usings"), script =>
+ {
+ var blocks = EnumerateUsingBlocks(context.RootNode);
+
+ foreach (var block in blocks)
+ {
+ var originalNodes = block.ToArray();
+ var sortedNodes = SortUsingBlock(originalNodes, context).ToArray();
+
+ for (var i = 0; i < originalNodes.Length; ++i)
+ script.Replace(originalNodes[i], sortedNodes[i].Clone());
+ }
+ });
+ }
+
+ private static AstNode FindUsingNodeAtCursor(RefactoringContext context)
+ {
+ // If cursor is inside using declaration
+ var locationAsIs = context.Location;
+ // If cursor is at end of line with using declaration
+ var locationLeft = new TextLocation(locationAsIs.Line, locationAsIs.Column - 1);
+
+ var possibleNodes = new[] { locationAsIs, locationLeft }
+ .Select(_ => context.RootNode.GetNodeAt(_, IsUsingDeclaration));
+ var usingNode = possibleNodes.Where(_ => _ != null).Distinct().SingleOrDefault();
+
+ return usingNode;
+ }
+
+ private static bool IsUsingDeclaration(AstNode node)
+ {
+ return node is UsingDeclaration || node is UsingAliasDeclaration;
+ }
+
+ private static IEnumerable> EnumerateUsingBlocks(AstNode root)
+ {
+ var alreadyAddedNodes = new HashSet();
+
+ foreach (var child in root.Descendants)
+ if (IsUsingDeclaration(child) && !alreadyAddedNodes.Contains(child)) {
+ var blockNodes = EnumerateUsingBlockNodes(child);
+
+ alreadyAddedNodes.UnionWith(blockNodes);
+ yield return blockNodes;
+ }
+ }
+
+ private static IEnumerable EnumerateUsingBlockNodes(AstNode firstNode)
+ {
+ for (var node = firstNode; IsUsingDeclaration(node); node = node.NextSibling)
+ yield return node;
+ }
+
+ private static IEnumerable SortUsingBlock(IEnumerable nodes, RefactoringContext context)
+ {
+ var infos = nodes.Select(_ => new UsingInfo(_, context));
+ var orderedInfos = infos.OrderBy(_ => _, new UsingInfoComparer());
+ var orderedNodes = orderedInfos.Select(_ => _.Node);
+
+ return orderedNodes;
+ }
+
+
+ private sealed class UsingInfo
+ {
+ public AstNode Node { get; private set; }
+
+ public string Alias { get; private set; }
+ public string Name { get; private set; }
+
+ public bool IsAlias { get; private set; }
+ public bool IsAssembly { get; private set; }
+ public bool IsSystem { get; private set; }
+
+ public UsingInfo(AstNode node, RefactoringContext context)
+ {
+ var importAndAlias = GetImportAndAlias(node);
+
+ Node = node;
+
+ Alias = importAndAlias.Item2;
+ Name = importAndAlias.Item1.ToString();
+
+ IsAlias = Alias != null;
+
+ var result = context.Resolve(importAndAlias.Item1) as NamespaceResolveResult;
+ var mainSourceAssembly = result != null ? result.Namespace.ContributingAssemblies.First() : null;
+ var unresolvedAssembly = mainSourceAssembly != null ? mainSourceAssembly.UnresolvedAssembly : null;
+ IsAssembly = unresolvedAssembly is DefaultUnresolvedAssembly;
+
+ IsSystem = IsAssembly && Name.StartsWith("System");
+ }
+
+ private static Tuple GetImportAndAlias(AstNode node)
+ {
+ var plainUsing = node as UsingDeclaration;
+ if (plainUsing != null)
+ return Tuple.Create(plainUsing.Import, (string)null);
+
+ var aliasUsing = node as UsingAliasDeclaration;
+ if (aliasUsing != null)
+ return Tuple.Create(aliasUsing.Import, aliasUsing.Alias);
+
+ throw new InvalidOperationException(string.Format("Invalid using node: {0}", node));
+ }
+ }
+
+ private sealed class UsingInfoComparer: IComparer
+ {
+ public int Compare(UsingInfo x, UsingInfo y)
+ {
+ if (x.IsAlias != y.IsAlias)
+ return x.IsAlias && !y.IsAlias ? 1 : -1;
+ else if (x.IsAssembly != y.IsAssembly)
+ return x.IsAssembly && !y.IsAssembly ? -1 : 1;
+ else if (x.IsSystem != y.IsSystem)
+ return x.IsSystem && !y.IsSystem ? -1 : 1;
+ else if (x.Alias != y.Alias)
+ return Comparer.Default.Compare(x.Alias, y.Alias);
+ else if (x.Name != y.Name)
+ return Comparer.Default.Compare(x.Name, y.Name);
+ else
+ return 0;
+ }
+ }
+ }
+}
diff --git a/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/SortUsingsTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/SortUsingsTests.cs
new file mode 100644
index 0000000000..0a47561ab2
--- /dev/null
+++ b/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/SortUsingsTests.cs
@@ -0,0 +1,109 @@
+using NUnit.Framework;
+using ICSharpCode.NRefactory.CSharp.Refactoring.CodeActions;
+
+namespace ICSharpCode.NRefactory.CSharp.CodeActions
+{
+ [TestFixture]
+ public class SortUsingsTests : ContextActionTestBase
+ {
+ [Test]
+ public void TestActiveWhenCursorAtUsing()
+ {
+ Test(@"using Sys$tem.Linq;
+using System;", @"using System;
+using System.Linq;");
+ }
+
+ [Test]
+ public void TestActiveWhenCursorBehindUsing()
+ {
+ Test(@"using System.Linq;$
+using System;", @"using System;
+using System.Linq;");
+ }
+
+ [Test]
+ public void TestInActiveWhenCursorOutsideUsings()
+ {
+ TestWrongContext(@"using System.Linq;
+using System;
+$");
+ }
+
+ [Test]
+ public void TestSortsAllUsingBlocksInFile()
+ {
+ Test(@"using $System.Linq;
+using System;
+
+namespace Foo
+{
+ using System.IO;
+ using System.Collections;
+}
+
+namespace Bar
+{
+ using System.IO;
+ using System.Runtime;
+ using System.Diagnostics;
+}", @"using System;
+using System.Linq;
+
+namespace Foo
+{
+ using System.Collections;
+ using System.IO;
+}
+
+namespace Bar
+{
+ using System.Diagnostics;
+ using System.IO;
+ using System.Runtime;
+}");
+ }
+
+ [Test]
+ public void TestAliasesGoesToTheEnd()
+ {
+ Test(@"$using Sys = System;
+using System;", @"using System;
+using Sys = System;");
+ }
+
+ [Test]
+ public void TestUnknownNamespacesGoesAfterKnownOnes()
+ {
+ Test(@"$using Foo;
+using System;", @"using System;
+using Foo;");
+ }
+
+ [Test]
+ public void TestMixedStuff()
+ {
+ Test(@"$using Foo;
+using System.Linq;
+using Sys = System;
+using System;
+using FooAlias = Foo;
+using Linq = System.Linq;", @"using System;
+using System.Linq;
+using Foo;
+using Linq = System.Linq;
+using Sys = System;
+using FooAlias = Foo;");
+ }
+
+ [Test]
+ public void TestPreservesEmptyLinesWhichIsInFactABug()
+ {
+ Test(@"$using System.Linq;
+
+using System;", @"using System;
+
+using System.Linq;");
+ }
+ }
+}
diff --git a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj
index f738c7aa97..f4296dd82b 100644
--- a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj
+++ b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj
@@ -360,6 +360,7 @@
+