C# code completion with NRefactory 5
Asked Answered
M

3

8

I just found out about NRefactory 5 and I would guess, that it is the most suitable solution for my current problem. At the moment I'm developing a little C# scripting application for which I would like to provide code completion. Until recently I've done this using the "Roslyn" project from Microsoft. But as the latest update of this project requires .Net Framework 4.5 I can't use this any more as I would like the app to run under Win XP as well. So I have to switch to another technology here.

My problem is not the compilation stuff. This can be done, with some more effort, by .Net CodeDomProvider as well. The problem ist the code completion stuff. As far as I know, NRefactory 5 provides everything that is required to provide code completion (parser, type system etc.) but I just can't figure out how to use it. I took a look at SharpDevelop source code but they don't use NRefactory 5 for code completion there, they only use it as decompiler. As I couldn't find an example on how to use it for code completion in the net as well I thought that I might find some help here.

The situation is as follows. I have one single file containing the script code. Actually it is not even a file but a string which I get from the editor control (by the way: I'm using AvalonEdit for this. Great editor!) and some assemblies that needs to get referenced. So, no solution files, no project files etc. just one string of source code and the assemblies.

I've taken a look at the Demo that comes with NRefactory 5 and the article on code project and got up with something like this:

var unresolvedTypeSystem = syntaxTree.ToTypeSystem();

IProjectContent pc = new CSharpProjectContent();

// Add parsed files to the type system
pc = pc.AddOrUpdateFiles(unresolvedTypeSystem);

// Add referenced assemblies:
pc = pc.AddAssemblyReferences(new CecilLoader().LoadAssemblyFile(
    System.Reflection.Assembly.GetAssembly(typeof(Object)).Location));

My problem is that I have no clue on how to go on. I'm not even sure if it is the right approach to accomplish my goal. How to use the CSharpCompletionEngine? What else is required? etc. You see there are many things that are very unclear at the moment and I hope you can bring some light into this.

Thank you all very much in advance!

Milly answered 20/9, 2012 at 5:41 Comment(1)
MonoDevelop uses the CSharpCompletionEngine class. Maybe that will demonstrate how it works.Hypotonic
F
3

Take a look at method ICSharpCode.NRefactory.CSharp.CodeCompletion.CreateEngine. You need to create an instance of CSharpCompletionEngine and pass in the correct document and the resolvers. I managed to get it working for CTRL+Space compltition scenario. However I am having troubles with references to types that are in other namespaces. It looks like CSharpTypeResolveContext does not take into account the using namespace statements - If I resolve the references with CSharpAstResolver, they are resolved OK, but I am unable to correctly use this resolver in code completition scenario...

UPDATE #1:

I've just managed to get the working by obtaining resolver from unresolved fail.

Here is the snippet:

var mb = new DefaultCompletionContextProvider(doc, unresolvedFile);
var resolver3 = unresolvedFile.GetResolver(cmp, loc); // get the resolver from unresolvedFile
var engine = new CSharpCompletionEngine(doc, mb, new CodeCompletionBugTests.TestFactory(resolver3), pctx, resolver3.CurrentTypeResolveContext );

Update #2:

Here is the complete method. It references classes from unit test projects, sou you would need to reference/copy them into your project:

public static IEnumerable<ICompletionData> DoCodeComplete(string editorText, int offset) // not the best way to put in the whole string every time
{

  var doc = new ReadOnlyDocument(editorText);
  var location = doc.GetLocation(offset);

  string parsedText = editorText; // TODO: Why there are different values in test cases?


  var syntaxTree = new CSharpParser().Parse(parsedText, "program.cs");
  syntaxTree.Freeze();
  var unresolvedFile = syntaxTree.ToTypeSystem();

  var mb = new DefaultCompletionContextProvider(doc, unresolvedFile);

  IProjectContent pctx = new CSharpProjectContent();
  var refs = new List<IUnresolvedAssembly> { mscorlib.Value, systemCore.Value, systemAssembly.Value}; 
  pctx = pctx.AddAssemblyReferences(refs);
  pctx = pctx.AddOrUpdateFiles(unresolvedFile);

  var cmp = pctx.CreateCompilation();

  var resolver3 = unresolvedFile.GetResolver(cmp, location);
  var engine = new CSharpCompletionEngine(doc, mb, new CodeCompletionBugTests.TestFactory(resolver3), pctx, resolver3.CurrentTypeResolveContext );


  engine.EolMarker = Environment.NewLine;
  engine.FormattingPolicy = FormattingOptionsFactory.CreateMono();

  var data = engine.GetCompletionData(offset, controlSpace: false);
  return data;

}

}

Hope it helps, Matra

Fat answered 2/10, 2012 at 19:35 Comment(0)
P
7

I've just compiled and example project that does C# code completion with AvalonEdit and NRefactory.

It can be found on Github here.

Perplexity answered 21/2, 2013 at 17:0 Comment(1)
You simply made my day! Many thanks for that great work. It took me a little to make it work. I was having problems since my test project was partially linked with another AvalonEdit version!Benzene
F
3

Take a look at method ICSharpCode.NRefactory.CSharp.CodeCompletion.CreateEngine. You need to create an instance of CSharpCompletionEngine and pass in the correct document and the resolvers. I managed to get it working for CTRL+Space compltition scenario. However I am having troubles with references to types that are in other namespaces. It looks like CSharpTypeResolveContext does not take into account the using namespace statements - If I resolve the references with CSharpAstResolver, they are resolved OK, but I am unable to correctly use this resolver in code completition scenario...

UPDATE #1:

I've just managed to get the working by obtaining resolver from unresolved fail.

Here is the snippet:

var mb = new DefaultCompletionContextProvider(doc, unresolvedFile);
var resolver3 = unresolvedFile.GetResolver(cmp, loc); // get the resolver from unresolvedFile
var engine = new CSharpCompletionEngine(doc, mb, new CodeCompletionBugTests.TestFactory(resolver3), pctx, resolver3.CurrentTypeResolveContext );

Update #2:

Here is the complete method. It references classes from unit test projects, sou you would need to reference/copy them into your project:

public static IEnumerable<ICompletionData> DoCodeComplete(string editorText, int offset) // not the best way to put in the whole string every time
{

  var doc = new ReadOnlyDocument(editorText);
  var location = doc.GetLocation(offset);

  string parsedText = editorText; // TODO: Why there are different values in test cases?


  var syntaxTree = new CSharpParser().Parse(parsedText, "program.cs");
  syntaxTree.Freeze();
  var unresolvedFile = syntaxTree.ToTypeSystem();

  var mb = new DefaultCompletionContextProvider(doc, unresolvedFile);

  IProjectContent pctx = new CSharpProjectContent();
  var refs = new List<IUnresolvedAssembly> { mscorlib.Value, systemCore.Value, systemAssembly.Value}; 
  pctx = pctx.AddAssemblyReferences(refs);
  pctx = pctx.AddOrUpdateFiles(unresolvedFile);

  var cmp = pctx.CreateCompilation();

  var resolver3 = unresolvedFile.GetResolver(cmp, location);
  var engine = new CSharpCompletionEngine(doc, mb, new CodeCompletionBugTests.TestFactory(resolver3), pctx, resolver3.CurrentTypeResolveContext );


  engine.EolMarker = Environment.NewLine;
  engine.FormattingPolicy = FormattingOptionsFactory.CreateMono();

  var data = engine.GetCompletionData(offset, controlSpace: false);
  return data;

}

}

Hope it helps, Matra

Fat answered 2/10, 2012 at 19:35 Comment(0)
J
2

NRefactory 5 is being used in SharpDevelop 5. The source code for SharpDevelop 5 is currently available in the newNR branch on github. I would take a look at the CSharpCompletionBinding class which has code to display a completion list window using information from NRefactory's CSharpCompletionEngine.

Jeuz answered 20/9, 2012 at 19:1 Comment(4)
Hi Matt. Unfortunately SharpDevelop 5 also requires .Net Framework 4.5 so I can't explore the solution on my machine. I hope to get my hands on a win 8 machine on the weekend to dig deeper into this. Can you narrow down the projects or dlls that are involved in the completion stuff? Besides CSharpBinding it seems to be at least the projects: Mono.Cecil, SharpDevelop, ICSharpCode.SharpDevelop and ICSharpCode.Core. Is this correct? (#D version 5.0.0.1472) It seems that the required classes are spread over several projects and namespaces. Thanks!Milly
You should be able to just use the NuGet packages for AvalonEdit and NRefactory. AvalonEdit has its own completion list window so it should just be a case of capturing text being entered and then feeding it to the completion engine. I will look at putting together an example.Jeuz
@MattWard ever got that example together? Thanks!Rayleigh
@Rayleigh - Lukas Buehler created an example - github.com/lukebuehler/NRefactory-Completion-SampleJeuz

© 2022 - 2024 — McMap. All rights reserved.