.NET Forum / Visual Studio.NET / Extensibility / February 2005
New language with managed code?
|
|
Thread rating:  |
ssyladin - 21 Feb 2005 17:41 GMT I'm trying to add a new language service to VS 2003 using C#. I've managed to associate the extension to my project and instantiate the core editor (using Dr. Ex's example of hosting the code editor in a toolwindow for the bulk of my code), and I try to set the language of the text buffer with GetLanguageServiceID , but I can't seem to figure out how to proffer my IVsLanguageInfo object as a service to VS.
I have my own set of code to implement a lexer and parser, so I don't need Babel. I would like to do this in 100% C#, except for the wizard created package installer. I'm fine with having to manually set registry entries vs. using VSIP helpers...
Can anyone help?
"Ed Dore [MSFT]" - 22 Feb 2005 00:34 GMT I did this a while back with the intent to build a language service for .RGS files (to colorize them), but never did finish it up. I did get it wired up properly though, at least to the point of dumping the text from IVsColorizer::ColorizeLine to Trace.WriteLine :-)
Basically I had two classes (RGSColorizer which is derived from IVsColorizer, and RGSLanguageService which is derived from IVsLanguageInfo). The RGSLanguageService is decorated with a Guid attribute. For example:
[Guid("BE8FD236-8677-4625-9E3C-F5A2E521CBF0")] public class RGSLanguageService : IVsLanguageInfo { ... }
To register the language service I derived a new class from RegistrationAttribute (see below) to set up the registry keys. Then it was just a matter of proffering the service in my package object's Initialize method which looked like the following:
... [ProvideLanguageService("RGS Language Service", typeof(RGSLanguageService), ".rgs", typeof(RGSServices), 300)] .... public class RGSServices : MSVSIP.Helper.Package, IVsInstalledProduct { .......... protected override void Initialize() { Trace.WriteLine (string.Format("Entering Initialize() of: {0}", this.ToString())); base.Initialize(); // Create and proffer the language service languageService = new RGSLanguageService(this); ((IServiceContainer)this).AddService(languageService.GetType(), languageService, true); } ........... }
[AttributeUsage(AttributeTargets.Class, AllowMultiple=true, Inherited=true)] public class ProvideLanguageServiceAttribute : RegistrationAttribute { private string langName; private string guidLangService; private uint langResID; private string guidPackage; private string fileextensions; // ; delimited. for example: ".rgs;.txt;.cpp"
private uint showCompletion; private uint showSmartIndent; private uint requestStockColors; private uint showHotURLs; private uint defaultToNonHotURLs; private uint defaultToInsertSpaces; private uint showDropdownBarOption; private uint singleCodeWindowOnly; private uint enableAdvMembersOption; private uint supportCF_HTML; private string GetGuidStrFromObject(object obj) { if (obj is string) return (string)obj; else if (obj is Type) return ((Type)obj).GUID.ToString("B"); else if (obj is Guid) return ((Guid)obj).ToString("B"); else throw new ArgumentException("Invalid ProvideLanguageService argument!"); }
public ProvideLanguageServiceAttribute(string langName, object guidLangService, string fileextensions, object guidPackage, uint langResID) { this.langName = langName; this.fileextensions = fileextensions; this.langResID = langResID; this.guidLangService = GetGuidStrFromObject(guidLangService); this.guidPackage = GetGuidStrFromObject(guidPackage); this.showCompletion = 0; this.showSmartIndent = 0; this.requestStockColors = 0; this.showHotURLs = 0; this.defaultToNonHotURLs = 0; this.defaultToInsertSpaces = 0; this.showDropdownBarOption = 0; this.singleCodeWindowOnly = 1; this.enableAdvMembersOption = 0; this.supportCF_HTML = 0; }
#region Properties public bool ShowCompletion { get { return showCompletion > 0; } set { if (value) showCompletion = 1; else showCompletion = 0; } }
public bool ShowSmartIndent { get { return showSmartIndent > 0; } set { if (value) showSmartIndent = 1; else showSmartIndent = 0; } }
public bool RequestStockColors { get { return requestStockColors > 0; } set { if (value) requestStockColors = 1; else requestStockColors = 0; } }
public bool ShowHotURLs { get { return showHotURLs > 0; } set { if (value) showHotURLs = 1; else showHotURLs = 0; } }
public bool DefaultToNonHotURLs { get { return defaultToNonHotURLs > 0; } set { if (value) defaultToNonHotURLs = 1; else defaultToNonHotURLs = 0; } }
public bool DefaultToInsertSpaces { get { return defaultToInsertSpaces > 0; } set { if (value) defaultToInsertSpaces = 1; else defaultToInsertSpaces = 0; } }
public bool ShowDropdownBarOption { get { return showDropdownBarOption > 0; } set { if (value) showDropdownBarOption = 1; else showDropdownBarOption = 0; } }
public bool SingleCodeWindowOnly { get { return singleCodeWindowOnly > 0; } set { if (value) singleCodeWindowOnly = 1; else singleCodeWindowOnly = 0; } } public bool EnableAdvMembersOption { get { return enableAdvMembersOption > 0; } set { if (value) enableAdvMembersOption = 1; else enableAdvMembersOption = 0; } } public bool SupportCF_HTML { get { return supportCF_HTML > 0; } set { if (value) supportCF_HTML = 1; else supportCF_HTML = 0; } }
#endregion Properties
public override void Register(RegistrationContext context) { // Register Language Service settings under <VS Reg Root>\Languages\Languages Services\<langsvc name> string regKeyName = string.Format("Languages\\Language Services\\{0}", langName); Key key = context.CreateKey(regKeyName); key.SetValue("", guidLangService); key.SetValue("LangResID", langResID); key.SetValue("Package", guidPackage); key.SetValue("ShowCompletion", showCompletion); key.SetValue("ShowSmartIndent", showSmartIndent); key.SetValue("RequestStockColors", requestStockColors); key.SetValue("ShowHotURLs", showHotURLs); key.SetValue("Default to Non Hot URLs", defaultToNonHotURLs); key.SetValue("DefaultToInsertSpaces", defaultToInsertSpaces); key.SetValue("ShowDropdownBarOption", showDropdownBarOption); key.SetValue("Single Code Window Only", singleCodeWindowOnly); key.SetValue("EnableAdvancedMembersOption", enableAdvMembersOption); key.SetValue("Support CF_HTML", supportCF_HTML); key.Close();
// Register File extensions under <VS Reg Root>\Languages\File Extensions\<file extension> char[] delimiters = {' ' , ',' , '.' , ':' , ';'}; string[] extensions = fileextensions.Split(delimiters,10); foreach (string s in extensions) { regKeyName = string.Format("Languages\\File Extensions\\.{0}", s); key = context.CreateKey(regKeyName); key.SetValue("", guidLangService); key.Close(); }
// Register language service under <VS Reg Root>\Services<langsvc guid> regKeyName = string.Format("Services\\{0}", guidLangService); key = context.CreateKey(regKeyName); key.SetValue("", guidPackage); key.SetValue("Name", langName); key.Close(); }
public override void Unregister(RegistrationContext context) { // Remove <VS Reg Root>\Languages\Language Services\<langName> key string regKeyName = string.Format("Languages\\Language Services\\{0}", langName); context.RemoveKey(regKeyName);
// Remove file extensions <VS Reg Root>\Languages\File Extensions\<file extension> char[] delimiters = {' ' , ',' , '.' , ':' , ';'}; string[] extensions = fileextensions.Split(delimiters,10); foreach (string s in extensions) { regKeyName = string.Format("Languages\\File Extensions\\.{0}", s); context.RemoveKey(regKeyName); }
// Remove entry under <VS Reg Root>\Services<langsvc guid> regKeyName = string.Format("Services\\{0}", guidLangService); context.RemoveKey(regKeyName); }
}
Hopefully, that'll get you pointed in the right direction.
Sincerely, Ed Dore [MSFT]
This post is 'AS IS' with no warranties, and confers no rights.
ssyladin - 22 Feb 2005 03:31 GMT Everything looks good and, with a little renaming I got things compiling and sort-of running.
Now, for the big question: in your IVsPackage::Initialize method you create the RGSLanguageService class with "this". How does one handle that in RGSLanguageService's constructor? To what end?
The big "sort-of" comment comes from VS initializing my package, then calling "GetColorizer" (returns null/0) and "GetCodeWindowManager" (same deal) but it gives me a nasty "The instruction at 0x... referenced memory at 0x0000000. The memory could not be read".
Now, looking at the VSIP docs, specifically "Checklist: Creating a Language Service", it says I need to do 3 things: 1) Implement IVsPackage (done) and implement IServiceProvider (done as part of IVsPackage) 2) Implement IVsLanguageInfo (done with stub functions) 3) Impelment your language (whoo hoo!)
Trying to figure out what was going wrong, I went back to my version and realized two things. The first was that I wasn't populating an entry in Services (not specifically mentioned in "Language Service Registry Information", but still common sense). The second was that I had created an editor/code window via the IVsEditorFactory::CreateEditorInstance method. A small spark of intuition later and the example you provided worked well, once I created a class that inherited from IVsCodeWindowManager and gave it empty functions.
Not to sound nitpicky, but hopefully to save others a couple of hours of headaches in the future, what specifically do we need to make sure to implement to ensure our language is loaded and used in a code window properly? Is the IVsEditorFactory a better way to go about things, or an "empty" implementation of IVsCodeWindowManager?
That aside, all is well in Language-ville and I'm chugging on implementing my IVsColorizer and staying sane.
Thanks a ton for the example Ed!!!
- Johann
> I did this a while back with the intent to build a language service for > .RGS files (to colorize them), but never did finish it up. I did get it [quoted text clipped - 297 lines] > > This post is 'AS IS' with no warranties, and confers no rights. "Ed Dore [MSFT]" - 24 Feb 2005 20:01 GMT Hi Johann,
I'm not sure why I wanted to pass the package object to the RGSLanguageService class. I suspect I initially though I'd need a reference back to the package object for some reason. Never did use it though :-)
With regards to proffering a service, there will be some walkthrough info with the Whidbey VSIP docs. I ran into the same problem the first time I tried to proffer a service. I couldn't figure out how the IDE could possibly know about the service (or the package that implemented it). Eventually, I found the registry info. I think Arron Marten blogged this topic a while back to : http://blogs.msdn.com/aaronmar/archive/2004/03/12/88646.aspx.
I'm not sure if a custom editor factory is a necessity, I'd have to go back and run some experiments to see there was a way to associate a particular file extension with the default text editor, and if it was smart enough to find and use the language service. I suspect not, but I haven't tried to confirm it.
Below is the CreateEditorInstance from that same sample. Note, I create a VsTextBuffer and a VsCodeWindow COM object via ILoadRegistry. I just noticed that I don't make a call to IVsTextBuffer::SetLanguageServiceID, so I'm thinking that the IDE does indeed know how to load the language service based on the file extension. Interesting....
public int CreateEditorInstance(uint grfCreateDoc, string pszMkDocument,string pszPhysicalView, IVsHierarchy pvHier, uint itemid, System.IntPtr punkDocDataExisting, out System.IntPtr ppunkDocView, out System.IntPtr ppunkDocData, out string pbstrEditorCaption, out Guid pguidCmdUI, out int pgrfCDW) { Trace.WriteLine(string.Format("Entering {0} CreateEditorInstance()", this.ToString()));
// Initialize to null ppunkDocView = new System.IntPtr (); ppunkDocData = new System.IntPtr (); pguidCmdUI = new Guid (); pgrfCDW = 0; pbstrEditorCaption = null;
// Validate inputs if ((grfCreateDoc & (NativeMethods.CEF_OPENFILE | NativeMethods.CEF_SILENT)) == 0) { return NativeMethods.E_INVALIDARG; }
if (punkDocDataExisting != IntPtr.Zero) { return NativeMethods.VS_E_INCOMPATIBLEDOCDATA; }
// Use ILocalRegistry to create text buffer and code window ServiceProvider sp = new ServiceProvider(vsServiceProvider); ILocalRegistry localRegistry = (ILocalRegistry)sp.GetService(typeof(ILocalRegistry)); Debug.Assert(localRegistry != null, "Unable to retrieve ILocalRegistry"); // Create the document (text buffer) IntPtr vsTextLines = IntPtr.Zero; Guid guidTextLines = typeof(IVsTextLines).GUID; int hr = localRegistry.CreateInstance(typeof(VsTextBufferClass).GUID, null, ref guidTextLines, NativeMethods.CLSCTX_INPROC_SERVER, out vsTextLines); Debug.Assert(hr == 0, "Failed to create instance of TextBuffer"); IVsTextLines textLines = Marshal.GetObjectForIUnknown(vsTextLines) as IVsTextLines;
// Site it, so it can find/query for various services IObjectWithSite ows = (IObjectWithSite)textLines; ows.SetSite(vsServiceProvider);
// Create the codewindow (editor) IntPtr vsCodeWindow = IntPtr.Zero; Guid guidCodeWindow = typeof(IVsCodeWindow).GUID; hr = localRegistry.CreateInstance(typeof(VsCodeWindowClass).GUID, null, ref guidCodeWindow, NativeMethods.CLSCTX_INPROC_SERVER, out vsCodeWindow); Debug.Assert(hr == 0, "Failed to create instance of CodeWindow"); IVsCodeWindow codeWindow = Marshal.GetObjectForIUnknown(vsCodeWindow) as IVsCodeWindow;
// Attach buffer to code window codeWindow.SetBuffer(textLines);
// use the CMDUIGUID_TextEditor guid to enable generic text editor keybindings pguidCmdUI = new Guid("8B382828-6202-11d1-8870-0000F87579D2"); // pass back both the editor and document ppunkDocView = vsCodeWindow; ppunkDocData = vsTextLines; pbstrEditorCaption = "";
return NativeMethods.S_OK; }
Ed Dore [MSFT]
This post is 'AS IS' with no warranties, and confers no rights.
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|