.NET Forum / Visual Studio.NET / Extensibility / April 2005
A tutorial on how to visually interact with the C# editor ?
|
|
Thread rating:  |
Thibaut - 20 Apr 2005 10:35 GMT Hello,
I'm trying to write an add-in which would require to change the appearance of code in the c# editor (or other code editors). What would be perfect would be to be able to change the background color of a specific area of code, or to underline it in a given color etc.
Can this be achieved easily ?
Any pointers would be most welcome
regards
thb
"Ed Dore [MSFT]" - 21 Apr 2005 18:21 GMT Hi Thibaut,
Colorization of text within the code editor is done by a language service. Each language integrated into the VS .Net IDE implements a VSIP package which proffers a language service. Language services themselves are not architected for this type of extensibility. To change the appearance of text in the code editor the way you describe, you would have to build a replacement language service for C#.
Implementing a language service requires the VSIP SDK, and is not a trivial task. With the latest Whidbey B2 VSIP SDK, there are some managed classes that help make this task a lot easier, and there is some good documentation/walkthrough information on leveraging the Managed Package Framework classes to do this. But currently, all the language service samples shipped in the SDK are written in C++.
From an addin standpoint, you could change the Font/Color information for various display items, similar to what you can do manually via the Tools.Options dialog. But I don't think that's really what you're looking for.
Sincerely, Ed Dore [MSFT]
This post is 'AS IS' with no warranties, and confers no rights.
Anna-Jayne Metcalfe - 21 Apr 2005 20:34 GMT Hi Ed,
> Colorization of text within the code editor is done by a language service. > Each language integrated into the VS .Net IDE implements a VSIP package > which proffers a language service. Language services themselves are not > architected for this type of extensibility. To change the appearance of > text in the code editor the way you describe, you would have to build a > replacement language service for C#. It does strike me that this is one area where the line between add-in and VSPackage implementations is somewhat blurry from an extensibility perspective.
We have a similar requirement - to drop a marker on a specified line, highlight a line of text and implement a tooltip on it. It's not an extravagant requirement by any means (we certainly don't need the functionality implementing a full blown language service offers), but currently requires access to VSIP interfaces. Nothing remotely suitable is present in the automation interface.
That in itself isn't a huge issue in some cases (aside from the learning curve and lack of time as always!) but there is another consideration for us - we currently target VS2002, VS2003 and VS2005 from the same binary, and the VS2002 VSIP SDK doesn't seem to be available anymore.
Assuming that a package created by VS2003 using the VS2003 VSIP SDK will not load under VS2002, that means we either have to drop support for VS2002 or not use VSIP.
As if that's not enough, we're also being asked about VC6 support, and although it's not difficult to build an add-in which supports every version of Visual Studio from 5.0 through to VS2005 (we've already prototyped that), the VSIP SDK for VC6 is again not available, and almost certainly a completely different animal from those for VS2002 onwards.
I hope this explanation illustrates why some of us need a little more from the automation interface, and can't simply switch to VSIP to achieve it. I'd rather hoped the automation interface in VS2005 would have been extended to address some of these limitations, but sadly that doesn't seem to be the case.
> From an addin standpoint, you could change the Font/Color information for > various display items, similar to what you can do manually via the > Tools.Options dialog. But I don't think that's really what you're looking > for. No, it isn't.
Kind Regards,
Anna-Jayne Metcalfe
Software/Product Development Consultant, Riverblade Limited. http://www.riverblade.co.uk
"Ed Dore [MSFT]" - 22 Apr 2005 18:02 GMT Hi Anna-Jayne,
There is the concept of a custom marker type, and it sounds like that may be what you're using in this case. Unfortunately, as you mentioned, this is a VSIP SDK construct and not exposed via automation. Any improvement in this area though, would certainly not solve your portability issue though. The common denominator for the legacy VS .NET IDE's would be the VSIP SDK, as the underlying support for custom markers was probably first introduced with VS .Net 2002.
The marker service in the core Whidbey IDE was enhanced a bit with some additional marker types, which I'm hoping to explore in the next couple weeks. And it too has evolved over the last couple releases, so I'm not sure how effective this solution would be with VS 2002.
The VSIP SDK in it's earliest forms were not designed for public consumption (almost no documentation, and no samples). It wasn't until VS 2002, where we actually saw the VSIP SDK that resembles what we have today. These earlier variants were made available to a few key partners, and these folks needed to work very closely with our devleopment team to effectively integrate their tools/components into the shell.
I'll pass along your comments to the VS extensibililty team though. I like the idea of exposing markers via the automation model.
Sincerely, Ed Dore [MSFT]
This post is 'AS IS' with no warranties, and confers no rights.
Scott Baldwin - 22 Apr 2005 22:48 GMT Ed,
I have implemented a language service in C# with colorization and the lot. I'm hosting an IVsTextLines in a code window, but I'm stumped on creating text markers though. I've tried using:
string msg = "Begin typing here..."; IVsTextLines lines; lines.InitializeContent(msg, msg.Length); lines.CreateLineMarker((int)MARKERTYPE.MARKER_CODESENSE_ERROR, 0, 0, 0, msg.Length, client, markers);
But I don't get any red squiggles. Is there something obvious that I'm missing? I've also tried creating my own client and marker service and have had no luck with that either.
Even better, do you have any samples of this in C#? I'm anxiously awaiting the beta 2 samples/documentation.
Thanks,
Scott
> Hi Anna-Jayne, > [quoted text clipped - 25 lines] > > This post is 'AS IS' with no warranties, and confers no rights. Anna-Jayne Metcalfe - 25 Apr 2005 22:26 GMT Hi Ed,
> There is the concept of a custom marker type, and it sounds like that may > be what you're using in this case. Unfortunately, as you mentioned, this [quoted text clipped - 4 lines] > as the underlying support for custom markers was probably first introduced > with VS .Net 2002. I believe so, although presumably something similar existed in VS6 (unless Visual Assist did its magic via Win32). We're not seriously expecting to be able to use marker type functionality in VS6 though...anything we do for that version is likely to be a restricted subset by necessity.
> The marker service in the core Whidbey IDE was enhanced a bit with some > additional marker types, which I'm hoping to explore in the next couple > weeks. And it too has evolved over the last couple releases, so I'm not > sure how effective this solution would be with VS 2002. Even with VS2002 its a moot point as we just can't get hold of the VSIP SDK for it. The earliest version available to us now is 2003, which quite frankly is a pain for those of us who can't dicate our customers platforms!
> The VSIP SDK in it's earliest forms were not designed for public > consumption (almost no documentation, and no samples). It wasn't until VS [quoted text clipped - 4 lines] > folks needed to work very closely with our devleopment team to effectively > integrate their tools/components into the shell. I rather expected that to be the case, though I'd still have loved to have had the opportunity to have a go. In that I wasn't alone, as the comments on articles such as Nick Hodapp's "Undocumented Visual C++" (http://www.codeproject.com/macro/openvc.asp) show.
> I'll pass along your comments to the VS extensibililty team though. I like > the idea of exposing markers via the automation model. That would be a great improvement. If they could do something about making VCProjectEngine events useable too (I don't know anyone who's managed to get them to work), what would be even better. :)
Kind Regards,
Anna-Jayne Metcalfe
Software/Product Development Consultant, Riverblade Limited. http://www.riverblade.co.uk
"Ed Dore [MSFT]" - 26 Apr 2005 23:55 GMT Hi Anna-Jayne,
Is there a particular event that you're interested in here, that doesn't seem to fire? The problem I typically see is that the events object doesn't stay in scope. You typically need to store away the particular events object as a member of your IDTExtensibility2 derived object so that your handlers don't get eaten by the garbage collector.
The VCProjectEngine object is actually a COM creatable beast, but from an addin, you would typically retrieve it using the VCProject.VCProjectEngine property. Depending upon your needs, you may want to simply create a helper function that initializes a private VCProjectEngineEvents member in your IDTExtensibility2 derived object.
Then call this helper from both your OnConnection implementation and a SolutionEvents.Open and SolutionEvents.ProjectAdded handler. That way you pretty much have all your bases covered depending upon how/when the addin is loaded. Below is an example I cooked up, that hooks the VCProjectEngineEvents.ItemAdded and ItemRemoved events when the addin is first loaded, or as soon as a VCProject has been loaded into the IDE.
namespace AnnaJayne { using System; using System.Collections; using System.Runtime.InteropServices; using System.Diagnostics; using Microsoft.VisualStudio.VCProjectEngine; using Extensibility; using EnvDTE;
[GuidAttribute("81D76454-D759-4553-83E3-304B2C050CEB"), ProgId("AnnaJayne.Connect")] public class Connect : Object, Extensibility.IDTExtensibility2 { private VCProjectEngineEvents vcProjEngineEvents; private EnvDTE.SolutionEvents solutionEvents;
public Connect() { vcProjEngineEvents = null; solutionEvents = null; }
void InitVCProjEngineEvents() { if (vcProjEngineEvents == null) { // Iterate through existing projects and attempt to retrieve the VCProjectEngine object, // and add our event handlers. Note, we only ever have to do this once. foreach(Project proj in applicationObject.Solution.Projects) { VCProject vcProj = proj.Object as VCProject; if (vcProj != null) { VCProjectEngine vcProjEngine = (VCProjectEngine)vcProj.VCProjectEngine; vcProjEngineEvents = (VCProjectEngineEvents)vcProjEngine.Events; vcProjEngineEvents.ItemAdded +=new _dispVCProjectEngineEvents_ItemAddedEventHandler(vcProjEngineEvents_ItemAdde d); vcProjEngineEvents.ItemRemoved +=new _dispVCProjectEngineEvents_ItemRemovedEventHandler(vcProjEngineEvents_ItemRe moved); break; } } } }
public void OnConnection(object application, Extensibility.ext_ConnectMode connectMode, object addInInst, ref System.Array custom) { applicationObject = (_DTE)application; addInInstance = (AddIn)addInInst;
// attempt to initialize VCProjectEngineEvents InitVCProjEngineEvents();
// Add handlers to call InitVCProjEngineEvents whenever a new solution or project is loaded. solutionEvents = applicationObject.Events.SolutionEvents; solutionEvents.Opened +=new _dispSolutionEvents_OpenedEventHandler(solutionEvents_Opened); solutionEvents.ProjectAdded +=new _dispSolutionEvents_ProjectAddedEventHandler(solutionEvents_ProjectAdded); }
public void OnDisconnection(Extensibility.ext_DisconnectMode disconnectMode, ref System.Array custom) { if (solutionEvents!=null) { solutionEvents.Opened -=new _dispSolutionEvents_OpenedEventHandler(solutionEvents_Opened); solutionEvents.ProjectAdded -=new _dispSolutionEvents_ProjectAddedEventHandler(solutionEvents_ProjectAdded); }
if (vcProjEngineEvents != null) { vcProjEngineEvents.ItemAdded -= new _dispVCProjectEngineEvents_ItemAddedEventHandler(vcProjEngineEvents_ItemAdde d); vcProjEngineEvents.ItemRemoved -=new _dispVCProjectEngineEvents_ItemRemovedEventHandler(vcProjEngineEvents_ItemRe moved); } }
public void OnAddInsUpdate(ref System.Array custom) { } public void OnStartupComplete(ref System.Array custom) { } public void OnBeginShutdown(ref System.Array custom) { } private _DTE applicationObject; private AddIn addInInstance;
private void vcProjEngineEvents_ItemAdded(object Item, object ItemParent) { Debug.WriteLine("ItemAdded event fired!"); }
private void vcProjEngineEvents_ItemRemoved(object Item, object ItemParent) { Debug.WriteLine("ItemRemoved event fired"); }
private void solutionEvents_Opened() { InitVCProjEngineEvents(); }
private void solutionEvents_ProjectAdded(Project Project) { InitVCProjEngineEvents(); } } }
Sincerely, Ed Dore [MSFT]
This post is 'AS IS' with no warranties, and confers no rights.
Anna-Jayne Metcalfe - 27 Apr 2005 21:19 GMT Hi Ed,
> Is there a particular event that you're interested in here, that doesn't > seem to fire? The problem I typically see is that the events object > doesn't > stay in scope. You typically need to store away the particular events > object as a member of your IDTExtensibility2 derived object so that your > handlers don't get eaten by the garbage collector. It's ItemAdded, ItemRenamed and ItemRemoved we're particularly interested in. Our add-in performs background PC-Lint analysis on the loaded solution while the IDE is in design mode, and to manage it all we maintain a model of the solution which mirrors that held by DTE. Obviously, when a project changes we'd like to ensure that our model stays up to date.
> The VCProjectEngine object is actually a COM creatable beast, but from an > addin, you would typically retrieve it using the VCProject.VCProjectEngine > property. Depending upon your needs, you may want to simply create a > helper > function that initializes a private VCProjectEngineEvents member in your > IDTExtensibility2 derived object. As this is somewhat beyond a simple add-in (34,000 lines of code so far!) we've designed the add-in with the usual add-in functionality (connection handling, command management/handling and event management/handling) in their own classes. Our event handling class (CAddInEventsManager) is responsible for maintaining the event sinks, and stores smart pointers for each event sink interface it manages (which it only releases when OnDisconnection() is called). As a result we're certain our events are being configured correctly, and (as you rightly point out) that our sink objects are not going out of scope prematurely.
(Incidentally, we did notice that VS2002 has a different set of interfaces for VCProjectEngine, albeit with exactly the same names and versions (the GUIDs are different). We've got to support both, as well of course as VS2005).
I've included an extract of the relevant class below. Any help you can give would be greatly appreciated.
Kind Regards,
Anna-Jayne Metcalfe
Software/Product Development Consultant, Riverblade Limited. http://www.riverblade.co.uk
typedef IDispEventImpl<1, CVCProjectEngineEventManager, &__uuidof(VCProjectEngineLibrary::_dispVCProjectEngineEvents), &VCProjectEngineLibrary::LIBID_VCProjectEngineLibrary, 7, 0> CVCProjectEngineEventsHander;
/// CVCProjectEngineEventManager handles events from the Visual Studio automation interface /// on behalf of CConnect and CAnalysisScheduler. /// class CVCProjectEngineEventManager : public CVCProjectEngineEventsHander { // Construction/destruction public: CVCProjectEngineEventManager(void);
~CVCProjectEngineEventManager(void);
// Data members protected: CComPtr<EnvDTE::_DTE> m_pDTE; ///< A pointer to the DTE interface of Visual Studio. //VCProjectEngineLibrary::IVCProjectEngineEventsPtr m_ptrVCProjectItemsEvents; ///< Allows us to receive ProjectItems events from Visual C++. IDispatchPtr m_ptrVCProjectItemsEvents;
CAnalysisScheduler* m_pAnalysisScheduler; ///< A pointer to the CAnalysisScheduler object owned by CConnect. CVsWindowManager* m_pWindowManager;
// Event sink map public: BEGIN_SINK_MAP(CVCProjectEngineEventManager) SINK_ENTRY_EX(1, __uuidof(VCProjectEngineLibrary::_dispVCProjectEngineEvents), 0x113, ItemAdded) END_SINK_MAP()
// Operations public: HRESULT OnConnection( CComPtr<EnvDTE::_DTE> pDTE, EnvDTE::ProjectPtr ptrProject, CAnalysisScheduler* pAnalysisScheduler, CVsWindowManager* pWindowManager);
void OnDisconnection(void);
// Event handlers protected: HRESULT __stdcall ItemAdded (IDispatch* Item, IDispatch* ItemParent); };
/// This method should be called by CConnect::OnConnection() to initialise our event sinks. /// HRESULT CVCProjectEngineEventManager::OnConnection( CComPtr<EnvDTE::_DTE> pDTE, EnvDTE::ProjectPtr ptrProject, CAnalysisScheduler* pAnalysisScheduler, CVsWindowManager* pWindowManager) { HRESULT hr = E_FAIL;
if (ptrProject == NULL) { ATLASSERT(false); return hr; }
m_pDTE = pDTE; ATLASSERT(m_pDTE != NULL);
if (m_pDTE != NULL) { m_pAnalysisScheduler = pAnalysisScheduler; ATLASSERT(NULL != m_pAnalysisScheduler);
m_pWindowManager = pWindowManager; ATLASSERT(NULL != m_pWindowManager);
try { VCProjectEngineLibrary::VCProjectPtr ptrVCProject = ptrProject->GetObject(); IfNullIssueError(ptrVCProject);
VCProjectEngineLibrary::VCProjectEnginePtr ptrVCProjectEngine = ptrVCProject->GetVCProjectEngine(); m_ptrVCProjectItemsEvents = ptrVCProjectEngine->GetEvents();
CVCProjectEngineEventsHander::DispEventAdvise(m_ptrVCProjectItemsEvents);
hr = S_OK; } catch (_com_error& e) { hr = e.Error(); } } return hr; }
/// This method should be called by CConnect::OnDisconnection() to close down our event sinks. void CVCProjectEngineEventManager::OnDisconnection(void) { try { // Unsubscribe for events CVCProjectEngineEventsHander::DispEventUnadvise(m_ptrVCProjectItemsEvents);
m_ptrVCProjectItemsEvents = NULL; } catch (_com_error& e) { ATLASSERT(NULL != m_pWindowManager); if (NULL != m_pWindowManager) { //lint -save -e437 (Warning -- Passing struct 'CStringT' to ellipsis) m_pWindowManager->WriteTextToStatusWindow( _T("ERROR: Unexpected exception %x in CVCProjectEngineEventManager::OnDisconnection(): %s"), e.Error(), e.ErrorMessage() ); //lint -restore } }
// Cleanup m_pAnalysisScheduler = NULL; m_pWindowManager = NULL; m_pDTE = NULL; }
HRESULT CVCProjectEngineEventManager::ItemAdded(IDispatch* Item, IDispatch* ItemParent) { EnvDTE::ProjectItemPtr ptrProjectItem(Item);
return S_OK; }
"Ed Dore [MSFT]" - 29 Apr 2005 07:16 GMT Hi Anna-Jayne,
Had some time to work up a sample C++ addin that pretty much does the same thing my C# version did. Made a few goofs along the way, but I do have it working now. If you'd like a copy, just fire me an email (remove that "online." from my address first), and I'll send along the .zip file.
Looking at your code again, the only thing I can see right off, is the version info in your typedef for CVCProjectEngineEventsHandler. You have a version of 7,0. But when you look at the typelib with the OLE/COM Object viewer, the version of the typelib is actually 1,0. You might want to give that a shot right off the bat. Here's a look at my sink object, which is patterned off the C++ version of the EventsWatcher automation sample:
class VCProjEngineEventsSink : public IDispEventImpl<1, VCProjEngineEventsSink, &__uuidof(VCProjectEngineLibrary::_dispVCProjectEngineEvents), &VCProjectEngineLibrary::LIBID_VCProjectEngineLibrary, 1, 0> { public: BEGIN_SINK_MAP(VCProjEngineEventsSink) SINK_ENTRY_EX(1, __uuidof(VCProjectEngineLibrary::_dispVCProjectEngineEvents), 0x0113, OnItemAdded) SINK_ENTRY_EX(1, __uuidof(VCProjectEngineLibrary::_dispVCProjectEngineEvents), 0x0114, OnItemRemoved) SINK_ENTRY_EX(1, __uuidof(VCProjectEngineLibrary::_dispVCProjectEngineEvents), 0x0115, OnItemRenamed) END_SINK_MAP()
void __stdcall OnItemAdded(IDispatch* Item, IDispatch* ItemParent) { ATLTRACE("OnItemAdded called!!!\n"); }
void __stdcall OnItemRemoved(IDispatch* Item, IDispatch* ItemParent) { ATLTRACE("OnItemRemoved called!!!\n"); }
void __stdcall OnItemRenamed(IDispatch* Item, IDispatch* ItemParent, BSTR OldName) { ATLTRACE("OnItemRenamed called!!!\n"); }
void SetOwner(CConnect* pAddin) { m_pAddin = pAddin; }
CConnect* m_pAddin; };
The only real problem I ran across was in retrieving the VCProject because I forgot to QI off the EnvDTE::Project's Object property. Below is the function I used to wire up the event sink above:
HRESULT CConnect::SinkVCProjEngineEvents() { HRESULT hr = S_OK;
// One time initialization to sink VCProjectEngineEvents if (m_pVCProjEngineEvents==NULL) { CComPtr<EnvDTE::_Solution> pSolution; CComPtr<EnvDTE::Projects> pProjects; long numProjects;
IfFailGo(m_pDTE->get_Solution(&pSolution)); IfFailGo(pSolution->get_Projects(&pProjects)); IfFailGo(pProjects->get_Count(&numProjects));
// look for a C++ project to retrieve VCProjectEngine from for (long i=1; i<=numProjects; i++) { CComPtr<EnvDTE::Project> pProject; CComVariant var(i); if SUCCEEDED(pProjects->Item(var, &pProject)) { // Note, we've got to QI the "Object" property for VCProject CComPtr<IDispatch> pDispProjectObj; if (SUCCEEDED(pProject->get_Object(&pDispProjectObj))) { CComPtr<VCProjectEngineLibrary::VCProject> pVCProject; if (SUCCEEDED(pDispProjectObj->QueryInterface(&pVCProject))) { // we've found a VCProject CComPtr<VCProjectEngineLibrary::VCProjectEngine> pVCProjEngine; if (SUCCEEDED(pVCProject->get_VCProjectEngine((IDispatch**)&pVCProjEngine))) { if (SUCCEEDED(pVCProjEngine->get_Events((IDispatch**)&m_pVCProjEngineEvents))) { m_VCProjEngineEventsSink.SetOwner(this); hr = m_VCProjEngineEventsSink.DispEventAdvise((IUnknown*)m_pVCProjEngineEvents.p) ; break; } } } } } } }
Error: return hr; }
Let me know if that doesn't do the trick.
Sincerely, 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 ...
|
|
|