Instruct R# to load external files for symbols

I would like to instruct R# to load certain .cs files that are not visible in the solution explorer for symbols at runtime. We already have a R# extension for other behaviors, so the behavior could be implemented in the plugin. Our projects generate a number of .cs files at compile time, and our mantra is that all build artifacts should exist outside of the solution/enlistment, so we include these files via <compile> msbuild tags. Unfortunately, we can not make these files visible in the solution explorer because the only way to do this outside of the root of the project is to have VS create a folder in the file system for them to be linked to (which would go against our mantra of no build artifacts in the enlistment).

I am trying to work around the bug http://youtrack.jetbrains.com/issue/RSRP-295992

Any ideas would be most appreciated!

5 comments
Comment actions Permalink

You might be able to do this with a plugin, by adding the files into a custom PSI Module.

A module is an entity that provides information to the PSI. (I'm presuming you're familiar with the PSI - it's ReSharper's understanding of the structure of your solution, including file abstract syntax trees, type information, usage references and so on). The most common modules are projects and assembly references. Project modules supply PSI file structures, assembly modules provide IL metadata. There are also a couple of other module types.

If you start ReSharper in internal mode (devenv /ReSharper.Internal), open a solution and then go to ReSharper->Internal->Windows->PSI Module Browser, you get a list of PSI modules, the references between them, and the files they contain. Most are project modules or assemblies. But you'll also see modules for solution folders (boring) and also "JavaScriptCore", "MSBuild module", "NAnt module", "Referenced external files". These last three might well be empty, but JavaScriptCore contains two files, ecma.js and EcmaScript5.js (web projects also includes a "Common DOM support module", containing ajax.js, dhtml.js, domcore.js and html5.js). These files (which you can't see from the module browser) are simple JS script files that define standard functions used in JS - eval, parseInt, isNaN, etc. By exposing these files in the module, ReSharper can parse them and make these functions available to e.g. intellisense.

You could do something similar - create a PSI module (by implementing IPsiModuleFactory) that includes the external CS files. ReSharper could then parse them and make the contents available for code completion.

The PSI module needs to be added as a reference to the project it should be included in. This is where things start getting complicated.

You can create a standalone PSI module by implementing IPsiModuleFactory. This is how JavaScriptCore is created, and (I think) this means the content is available to all consumers (code completion). In other words, the system treats this module as a "source" module, in that it provides source files to be parsed and added to the system.

Alternatively, you can associate your module with an existing project. When ReSharper wants a PSI module, it creates an instance of IProjectPsiModuleHandler, from a top-level set of handlers. It then passes the handler to all instances of IProjectPsiModuleFilter registered with the system (any classes implementing the system marked with [SolutionComponent]). This filter returns a new handler (we're implementing the Decorator pattern here) and/or also, confusingly, a decorator, that implements IPsiModuleDecorator (this design is marked for cleaning up at some point).

I think for your situation, you can implement either - a delegating handler, or a decorator.

If you use the decorator, you need to implement IPsiModuleDecorator, and you get to override the module references, and the available source files. You should be able to return the newly generated source files here. You would most likely create the IPsiSourceFile by creating an instance of PsiSourceFilePath. Most of the parameters can be injected from a component constructor, or fetched with Shell.GetComponent<T>().

Or, you can implement IProjectPsiModuleHandler (deriving from DelegatingProjectPsiModuleHandler would be best here) and override GetAllModules to add your custom module to the output. And your custom module would then return the generated source files.

I'm not entirely sure what would be the difference in these two approaches. It might be that you need to implement a module because you don't actually have any source files in the project to return. I'd try to implement IPsiModuleDecorator first, and see what that gives you. It might be that implementing a custom module just gives you a bit more flexibility.

Once you've done this, you need to notify the system when your files change. You need a component that implements IChangeProvider. It needs to take in an instance of ChangeManager, and registers itself as a change provider, and register a dependency between yourself and an instance of PsiModules. Whenever a file changes, tell the change manager that you've changed by passing yourself into ChangeManager.OnProviderChanged. You'll need to build an object to pass in, and that should be a PsiModuleChange (DependentFilesModuleFactory is worth checking out here, although the change management is wired up externally, due to it being a handler for Misc Files. Also, ReSharper->Internal->View Change Manager Graph is very useful. yEd is a handy tool for viewing the generated graph)

So, I think you should be able to do it, but it's not exactly trivial. I've not tried this before, so some of these details may be off. The best thing to do is dive in with dotPeek and try to follow what's going on, starting with PsiModules and ProjectPsiModuleFactory.TryCreateProvider.

Also, the ForTea plugin (https://github.com/MrJul/ForTea) has an example of a custom module (it adds support for ReSharper in T4 files, which can have different references to the rest of the project - the module contains the .t4 file and tracks all of those references).

Hope this helps, please ask if you need more detail.

Thanks
Matt

0
Comment actions Permalink

Thanks for the very detailed response!

After continued digging via dotPeek I did find the IProjectPsiModuleProviderFilter and IPsiModuleDecorator which allowed me to implement OverrideSourceFiles. It works great! The only problem is that the types for the included files do not show up in the "Go To Type", "Go To Symbol", or "Go To File" dialogs, nor can I use "Go To Definition" to get to the source files. Any ideas on how I can get these features to see my files?

0
Comment actions Permalink
I think that depends on what you return from IPsiSourceFileProperties. I think if you return true from ProvidesCodeModel, it should allow navigation.
0
Comment actions Permalink

Here are the properties for my IPsiSourceFileProperties:


            public bool ShouldBuildPsi
            {
                get { return true; }
            }

            public bool IsGeneratedFile
            {
                get { return true; }
            }

            public bool ProvidesCodeModel
            {
                get { return true; }
            }

            public bool IsNonUserFile
            {
                get { return false; }
            }

0
Comment actions Permalink

OK. Maybe that's not it. I've had a dig around, and firstly, I don't think you'll get these files to show up in goto file popup. That search looks at the files that are in the project model, and these files won't be (they're not part of the VS solution/project hierarchy). Secondly, I have no idea :)

I don't know the path the extra source files you provide take as they flow through the system to where the goto code will look at types and symbols. Do you have a repro that I could use to dig further? You can email me at resharper-plugins at jetbrains.com.

The only suggestion I can provide right now is to also return true from IPsiSourceFile.IsICacheParticipant. If this is true (with ShouldBuildPsi and ProvidesCodeModel), then the files are processed by the PSI cache. I don't know if that means the PSI structures from these files will be cached, but the goto type/symbol code does use the cache.

Thanks
Matt

0

Please sign in to leave a comment.