Skip to content

Composable subset of the VS-Editor platform that's UI-agnostic to allow cross-platform unit-testing scenarios

License

Notifications You must be signed in to change notification settings

garuma/MiniEditor

Repository files navigation

MiniEditor

NuGet

Composable subset of the Visual Studio Editor platform that's UI-agnostic to allow cross-platform unit-testing scenarios

Motivation

If you are creating editor extensions for both Visual Studio and Visual Studio for Mac that are cross-platform, IDE-agnostic and UI-agnostic then this library should allow you to instantiate and thus unit-test them.

The library doesn't require a specific test framework and uses VS-mef to compose itself at runtime.

The main testing scenarios supported are currently:

  • Low-level usage of interfaces such as ITextDocument, ITextBuffer, ITextSnapshot and so on.
  • Async completion Intellisense providers.

Setup

For our scenario we assume your editor extensions are in one .NET library (.NET standard or .NET framework) and your test project (using whatever testing library) references it so that it's copied in its output.

Depending on your testing needs, you might need to supply a few MEF parts of your own for things to work:

  • If you are trying to test async completion extensions, you should export a JoinableTaskContext (see vs-threading repository for more information):
usingSystem.ComponentModel.Composition;/* Set the field to an instance of the class with whatever option you want *before* initializing the composition, usually in your unit test framework setup fixture*/[Export]publicstaticMicrosoft.VisualStudio.Threading.JoinableTaskContextMefJoinableTaskContext=null;
  • You might be referencing MEF parts that are only available in the full IDE, a classic example is some Content-Type which you may need to export yourself manually:
usingSystem.ComponentModel.Composition;usingMicrosoft.VisualStudio.Utilities;[Export][Name("xml")][BaseDefinition("text")]publicstaticreadonlyContentTypeDefinitionXmlContentTypeDefinition=null;

MiniEditor also provides a very basic file-system abstraction so that you can instantiate, load and reload ITextDocument instances from in-memory content instead of from disk:

usingSystem.IO;usingSystem.Text;usingMicrosoft.VisualStudio.Text;usingMicrosoft.VisualStudio.MiniEditor;// Register implementation staticallyMiniEditorSetup.FileSystem=newDummyFileSystem();classDummyFileSystem:IFileSystemAbstraction{publicStreamOpenFile(stringfilePath,outDateTimelastModifiedTimeUtc,outlongfileSize){lastModifiedTimeUtc=DateTime.UtcNow;varbytes=Encoding.UTF8.GetBytes("Hello World");fileSize=bytes.Length;returnnewMemoryStream(bytes);}publicvoidPerformSave(ITextSnapshottextSnapshot,FileModefileMode,stringfilePath,Encodingencoding,boolcreateFolder){Console.WriteLine(textSnapshot.GetText());}}

Usage

With setup out of the way, the first thing you need to do is create a MEF composition container to host the editor subset from the library and (if needed) the extensions that you are providing.

Usually you would end up doing this in your unit-test framework equivalent of an initialization method and it would look a bit like this:

usingMicrosoft.VisualStudio.MiniEditor;staticEditorEnvironmentEditorEnvironment{get;privateset;}publicstaticvoidInitializeMiniEditor(){// Remember to initialize that JoinableTaskContext if you need itMefJoinableTaskContext=newJoinableTaskContext();// Create the MEF composition// can be awaited instead if your framework supports itEditorEnvironment=EditorEnvironment.InitializeAsync("Your.Assembly.With.Extensions.dll","This.Assembly.With.The.Tests.dll").Result;if(EditorEnvironment.CompositionErrors.Length>0){Console.WriteLine("Composition Errors:");foreach(varerrorinEditorEnvironment.CompositionErrors)Console.WriteLine("\t"+error);}// Register your own logging mechanism to print eventual errors// in your extensionsvarerrorHandler=EditorEnvironment.GetEditorHost().GetService<EditorHostExports.CustomErrorHandler>();errorHandler.ExceptionHandled+=(s,e)=>Console.WriteLine(e.Exception);}

You can then get all the pieces you need by retrieving an EditorEnvironment.Host instance and querying it using its GetService<T> method. For instance, here is an helper class that references a few well-known editor services:

classEditorCatalog{publicEditorCatalog(EditorEnvironmentenv)=>Host=env.GetEditorHost();EditorEnvironment.HostHost{get;}publicITextViewFactoryServiceTextViewFactory=>Host.GetService<ITextViewFactoryService>();publicITextDocumentFactoryServiceTextDocumentFactoryService=>Host.GetService<ITextDocumentFactoryService>();publicIFileToContentTypeServiceFileToContentTypeService=>Host.GetService<IFileToContentTypeService>();publicITextBufferFactoryServiceBufferFactoryService=>Host.GetService<ITextBufferFactoryService>();publicIContentTypeRegistryServiceContentTypeRegistryService=>Host.GetService<IContentTypeRegistryService>();publicIAsyncCompletionBrokerAsyncCompletionBroker=>Host.GetService<IAsyncCompletionBroker>();publicIClassifierAggregatorServiceClassifierAggregatorService=>Host.GetService<IClassifierAggregatorService>();publicIClassificationTypeRegistryServiceClassificationTypeRegistryService=>Host.GetService<IClassificationTypeRegistryService>();publicIBufferTagAggregatorFactoryServiceBufferTagAggregatorFactoryService=>Host.GetService<IBufferTagAggregatorFactoryService>();}

Examples

Creating a ITextBuffer and ITextView

IContentTypecontentType=EditorCatalog.ContentTypeRegistryService.GetContentType("MyContentType");ITextBufferbuffer=EditorCatalog.BufferFactoryService.CreateTextBuffer(content,contentType);ITextViewtextView=EditorCatalog.TextViewFactory.CreateTextView(buffer);

Creating a text document

// Mock content associated to `filePath` via `MiniEditorSetup.FileSystem`IContentTypecontentType=EditorCatalog.FileToContentTypeService.GetContentTypeForFilePath(filePath);ITextDocumentdocument=EditorCatalog.TextDocumentFactoryService.CreateAndLoadTextDocument(filePath,contentType);

Instantiating an async completion broker

usingMicrosoft.VisualStudio.Language.Intellisense.AsyncCompletion;usingMicrosoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data;// Use previous examples to get those objectsITextBufferbuffer=/* ... */;ITextViewview=/* ... */;intcaretPosition=/* where the cursor would be in your text view */;IAsyncCompletionBrokerbroker=EditorCatalog.AsyncCompletionBroker;ITextSnapshotsnapshot=buffer.Snapshot;vartrigger=newCompletionTrigger(CompletionTriggerReason.Invoke,snapshot);varcontext=awaitbroker.GetAggregatedCompletionContextAsync(textView,trigger,newSnapshotPoint(snapshot,caretPosition),CancellationToken.None);// Your completion source should have hopefully filled this up with stuffvarcompletionItems=context.CompletionContext.Items;

About

Composable subset of the VS-Editor platform that's UI-agnostic to allow cross-platform unit-testing scenarios

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 3

  •  
  •  
  •  

Languages