9

Je suis en train de créer une VSPackage d'extension pour VS2017 (en C#) qui convertir des données binaires en XML, ouvre que dans le défaut VS éditeur XML et un service de langage XML, et convertit ensuite en arrière en binaire lors de l'enregistrement.VSX: Comment puis-je réutiliser l'éditeur XML existant pour gérer les fichiers binaires convertis en XML?

Cependant, j'ai du mal à la ligne quelles mesures seraient nécessaires pour cela. Je pensais à ce qui suit pour l'instant lors de la création d'un nouvel éditeur dans l'usine de l'éditeur:

  • Créer un nouveau tampon texte
  • Nourrissez avec des données XML converties
  • Créer éditeur de base
  • Nourrissez avec le texte tampon

En ce moment, ma tentative ressemble à ceci:

private MyPackage _package; // Filled via constructor 
private IServiceProvider _serviceProvider; // Filled via SetSite 

public int CreateEditorInstance(uint grfCreateDoc, string pszMkDocument, string pszPhysicalView, 
    IVsHierarchy pvHier, uint itemid, IntPtr punkDocDataExisting, out IntPtr ppunkDocView, 
    out IntPtr ppunkDocData, out string pbstrEditorCaption, out Guid pguidCmdUI, out int pgrfCDW) 
{ 
    // Initialize and validate parameters. 
    ppunkDocView = IntPtr.Zero; 
    ppunkDocData = IntPtr.Zero; 
    pbstrEditorCaption = String.Empty; 
    pguidCmdUI = Guid.Empty; 
    pgrfCDW = 0; 
    VSConstants.CEF createDocFlags = (VSConstants.CEF)grfCreateDoc; 
    if (!createDocFlags.HasFlag(VSConstants.CEF.OpenFile) && !createDocFlags.HasFlag(VSConstants.CEF.Silent)) 
     return VSConstants.E_INVALIDARG; 
    if (punkDocDataExisting != IntPtr.Zero) 
     return VSConstants.VS_E_INCOMPATIBLEDOCDATA; 

    // Create a sited IVsTextBuffer storing the converted data with the XML data and language service set. 
    IVsTextLines textLines = _package.CreateComInstance<VsTextBufferClass, IVsTextLines>(); 
    SiteObject(textLines); 
    string xmlText = BinaryXmlData.GetXmlString(pszMkDocument); 
    textLines.InitializeContent(xmlText, xmlText.Length); 
    ErrorHandler.ThrowOnFailure(textLines.SetLanguageServiceID(ref Guids.XmlLanguageServiceGuid)); 

    // Instantiate a sited IVsCodeWindow and feed it with the text buffer. 
    IVsCodeWindow codeWindow = _package.CreateComInstance<VsCodeWindowClass, IVsCodeWindow>(); 
    SiteObject(codeWindow); 
    codeWindow.SetBuffer(textLines); 

    // Return the created instances to the caller. 
    ppunkDocView = Marshal.GetIUnknownForObject(codeWindow); 
    ppunkDocData = Marshal.GetIUnknownForObject(textLines); 

    return VSConstants.S_OK; 
} 

private void SiteObject(object obj) 
{ 
    (obj as IObjectWithSite)?.SetSite(_serviceProvider); 
} 

// --- CreateComInstance is a method on my package ---- 
internal TInterface CreateComInstance<TClass, TInterface>() 
{ 
    Guid guidT = typeof(TClass).GUID; 
    Guid guidInterface = typeof(TInterface).GUID; 

    TInterface instance = (TInterface)CreateInstance(ref guidT, ref guidInterface, typeof(TInterface)); 
    if (instance == null) 
     throw new COMException($"Could not instantiate {typeof(TClass).Name}/{typeof(TInterface).Name}."); 

    return instance; 
} 

Lorsque j'essaie d'ouvrir explicitement un fichier avec mon éditeur, il indique "Le fichier ne peut pas être ouvert avec l'éditeur sélectionné. S'il vous plaît choisir un autre éditeur. "Le message n'a pas de sens pour moi, j'ai essayé d'ouvrir les données XML avec l'éditeur XML, mais il essaie toujours d'ouvrir un éditeur de texte avec les données binaires. tout ce que je pouvais penser pour l'alimenter converti les données. il semble que cette façon est pas la bonne.

  • Comment pourrais-je ajouter des étapes inbetween pour récupérer les données binaires, convertir rapidement en XML, puis alimenter le XML éditeur
  • Comment est-ce que je le stockerais comme binaire quand l'éditeur XML enregistre le fichier?
  • Est-il possible de réutiliser le XML? éditeur et services linguistiques pour cela?

Je suis désolé si ces questions nécessitent des réponses longues; Je serais déjà heureux si je pouvais être pointé dans la bonne direction ou à une extension déjà ouverte de manière similaire (en convertissant les données du fichier avant de les afficher dans un éditeur de code VS).

+0

Je ne sais pas, mais je vous pirater autour d'elle en l'enregistrant sous forme de fichier XML dans un répertoire temporaire, ouvrez-le dans l'éditeur, et regarder le fichier pour changements. Pas élégant, mais faisable rapidement. Peut-être que quelqu'un sait comment faire fonctionner l'itinéraire élégant ... – Will

+0

Ouais, j'ai pensé à ça aussi me sentir battu par VSX, mais ensuite je peux garder mon programme externe actuel pour modifier les fichiers; de plus je ne suis pas sûr que ces hacks soient plus faciles à mettre en place par la suite ... –

Répondre

5

L'idée générale est de laisser l'éditeur Xml faire ce qu'il fait habituellement: ouvrir un document .

Dans votre cas, si je comprends bien, vous n'avez pas un document Xml physique, vous devez en créer un. Un document est quelque chose (il ne doit pas être un fichier physique) qui est enregistré dans Visual Studio Running Object Table.

Une fois que vous avez un document, vous pouvez demander au shell de l'ouvrir. Vous pouvez utiliser à nouveau le ROT pour gérer BeforeSave and AfterSave events. Voici quelques exemples de code qui devrait faire tout cela:

public int CreateEditorInstance(uint grfCreateDoc, string pszMkDocument, string pszPhysicalView, IVsHierarchy pvHier, uint itemid, IntPtr punkDocDataExisting, out IntPtr ppunkDocView, out IntPtr ppunkDocData, out string pbstrEditorCaption, out Guid pguidCmdUI, out int pgrfCDW) 
{ 
    ppunkDocView = IntPtr.Zero; 
    ppunkDocData = IntPtr.Zero; 
    pbstrEditorCaption = null; 
    pguidCmdUI = Guid.Empty; 
    pgrfCDW = 0; 

    // create your virtual Xml buffer 
    var data = Package.CreateComInstance<VsTextBufferClass, IVsTextLines>(); 
    SiteObject(data); 

    // this is where you're supposed to build your virtual Xml content from your binary data 
    string myXml = "<root>blah</root>"; 
    data.InitializeContent(myXml, myXml.Length); 
    var dataPtr = Marshal.GetIUnknownForObject(data); 

    // build a document and register it in the Running Object Table 
    // this document has no hierarchy (it will be handled by the 'Miscellaneous Files' fallback project) 
    var rotFlags = _VSRDTFLAGS.RDT_ReadLock | _VSRDTFLAGS.RDT_VirtualDocument; 

    // come up with a moniker (which will be used as the caption also by the Xml editor) 
    // Note I presume the moniker is a file path, wich may not always be ok depending on your context 
    var virtualMk = Path.ChangeExtension(pszMkDocument, ".xml"); 
    var rot = (IVsRunningDocumentTable)_sp.GetService(typeof(SVsRunningDocumentTable)); 
    int hr = rot.RegisterAndLockDocument((uint)rotFlags, virtualMk, null, VSConstants.VSITEMID_NIL, dataPtr, out uint docCookie); 
    if (hr != 0) 
     return hr; 

    try 
    { 
     // ask Visual Studio to open that document 
     var opener = (IVsUIShellOpenDocument)_sp.GetService(typeof(SVsUIShellOpenDocument)); 
     var view = VSConstants.LOGVIEWID_Primary; 
     opener.OpenDocumentViaProject(virtualMk, ref view, 
      out Microsoft.VisualStudio.OLE.Interop.IServiceProvider psp, 
      out IVsUIHierarchy uiHier, 
      out uint id, 
      out IVsWindowFrame frame); 
     if (frame != null) 
     { 
      // Hmm.. the dirty bit (the star after the caption) is not updated by the Xml Editor... 
      // If you close the document (or close VS), it does update it, but it does not react when we type in the editor. 
      // This is unexpected, so, let's do the "dirty" work ourselves 
      // hook on text line events from the buffer 
      var textLineEvents = new TextLineEvents((IConnectionPointContainer)data); 

      // we want to know when to unadvise, to hook frame events too 
      ((IVsWindowFrame2)frame).Advise(textLineEvents, out uint frameCookie); 

      textLineEvents.LineTextChanged += (sender, e) => 
      { 
       // get the dirty bit and override the frame's dirty state 
       ((IVsPersistDocData)data).IsDocDataDirty(out int dirty); 
       frame.SetProperty((int)__VSFPROPID2.VSFPROPID_OverrideDirtyState, dirty != 0 ? true : false); 
      }; 

      // now handle save events using the rot 
      var docEventHandler = new RotDocumentEvents(docCookie); 
      docEventHandler.Saving += (sender, e) => 
      { 
       // this is where you can get the content of the data and save your binary data back 
       // you can use Saved or Saving 

      }; 

      docEventHandler.Saved += (sender, e) => 
      { 
       // manual reset of dirty bit... 
       frame.SetProperty((int)__VSFPROPID2.VSFPROPID_OverrideDirtyState, false); 
      }; 
      rot.AdviseRunningDocTableEvents(docEventHandler, out uint rootCookie); 

      frame.Show(); 
     } 
    } 
    finally 
    { 
     rot.UnlockDocument((uint)_VSRDTFLAGS.RDT_ReadLock, docCookie); 
    } 
    return VSConstants.S_OK; 
} 

private class TextLineEvents : IVsTextLinesEvents, IVsWindowFrameNotify, IVsWindowFrameNotify2 
{ 
    public event EventHandler LineTextChanged; 
    private uint _cookie; 
    private IConnectionPoint _cp; 

    public TextLineEvents(IConnectionPointContainer cpc) 
    { 
     var textLineEventsGuid = typeof(IVsTextLinesEvents).GUID; 
     cpc.FindConnectionPoint(ref textLineEventsGuid, out _cp); 
     _cp.Advise(this, out _cookie); 
    } 

    public void OnChangeLineText(TextLineChange[] pTextLineChange, int fLast) => LineTextChanged?.Invoke(this, EventArgs.Empty); 

    public int OnClose(ref uint pgrfSaveOptions) 
    { 
     _cp.Unadvise(_cookie); 
     return VSConstants.S_OK; 
    } 

    public void OnChangeLineAttributes(int iFirstLine, int iLastLine) { } 
    public int OnShow(int fShow) => VSConstants.S_OK; 
    public int OnMove() => VSConstants.S_OK; 
    public int OnSize() => VSConstants.S_OK; 
    public int OnDockableChange(int fDockable) => VSConstants.S_OK; 
} 

private class RotDocumentEvents : IVsRunningDocTableEvents3 
{ 
    public event EventHandler Saved; 
    public event EventHandler Saving; 

    public RotDocumentEvents(uint docCookie) 
    { 
     DocCookie = docCookie; 
    } 

    public uint DocCookie { get; } 

    public int OnBeforeSave(uint docCookie) 
    { 
     if (docCookie == DocCookie) 
     { 
      Saving?.Invoke(this, EventArgs.Empty); 
     } 
     return VSConstants.S_OK; 
    } 

    public int OnAfterSave(uint docCookie) 
    { 
     if (docCookie == DocCookie) 
     { 
      Saved?.Invoke(this, EventArgs.Empty); 
     } 
     return VSConstants.S_OK; 
    } 

    public int OnAfterFirstDocumentLock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining) => VSConstants.S_OK; 
    public int OnBeforeLastDocumentUnlock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining) => VSConstants.S_OK; 
    public int OnAfterAttributeChange(uint docCookie, uint grfAttribs) => VSConstants.S_OK; 
    public int OnBeforeDocumentWindowShow(uint docCookie, int fFirstShow, IVsWindowFrame pFrame) => VSConstants.S_OK; 
    public int OnAfterDocumentWindowHide(uint docCookie, IVsWindowFrame pFrame) => VSConstants.S_OK; 
    public int OnAfterAttributeChangeEx(uint docCookie, uint grfAttribs, IVsHierarchy pHierOld, uint itemidOld, string pszMkDocumentOld, IVsHierarchy pHierNew, uint itemidNew, string pszMkDocumentNew) => VSConstants.S_OK; 
} 
+0

Wow, ça marche parfaitement!Il y a juste une petite question à gauche; est-il possible d'obtenir l'astérisque «has unsaved changes» pour travailler dans le titre de l'éditeur? Et: Comment avez-vous eu connaissance de tout cela, de toute littérature que je devrais lire? : D –

+0

Vous avez raison. C'est inattendu, peut-être parce que ce n'est pas un vrai fichier? Le tampon a le bit sale, mais XmlEditor ne l'utilise pas lorsque nous changeons le texte (il fonctionne quand nous fermons le fichier/projet/solution, nous recevons un avertissement et le * est défini). J'ai ajouté une solution de contournement ... Concernant le développement VS, il n'y a pas de littérature spécifique au-delà du SDK officiel (qui est pool). Juste l'expérience (et beaucoup de spéléologie dans les assemblages Visual Studio en utilisant des outils tels que Reflector, ILSpy, etc. :-) Notez que ce sont les "anciennes" interfaces. Le plus récent WPF et al. sont mieux. –

+0

C'est vraiment un comportement étrange, j'essaie votre solution chaque minute. Pendant ce temps, j'ai essayé de traquer un bug où VS, pour une raison mystérieuse, en sauvegardant voulu écrire un fichier Temp.txt avec le contenu XML dans tout ce qui est le répertoire de travail actuel il semble - surgissant une boîte de message, il ne pouvait pas stocker quelque chose dans C: \ Windows \ system32 \ Temp.txt ou le chemin VS devenv.exe. Je n'ai certainement pas ajouté un tel code, je le convertis en mémoire, et il semble que cela se passe entre Sauvegarde et Sauvegarde ... peut-être que j'essaye juste de changer le répertoire actuel en un temp réel ...: O Bounty est attribué très bientôt loin! –