feat: add DSH IDE open-file references
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio;
|
||||
using Microsoft.VisualStudio.Shell;
|
||||
using Microsoft.VisualStudio.Shell.Interop;
|
||||
using Microsoft.VisualStudio.TextManager.Interop;
|
||||
using Task = System.Threading.Tasks.Task;
|
||||
|
||||
namespace Prophet.CoopWithDsh
|
||||
{
|
||||
[PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
|
||||
[InstalledProductRegistration("DSH Open File Bridge", "Publishes open file metadata for DeepSeek Harness", "0.1.0")]
|
||||
[ProvideAutoLoad(UIContextGuids80.NoSolution, PackageAutoLoadFlags.BackgroundLoad)]
|
||||
[ProvideAutoLoad(UIContextGuids80.SolutionExists, PackageAutoLoadFlags.BackgroundLoad)]
|
||||
[Guid(PackageGuidString)]
|
||||
public sealed class CoopWithDshPackage : AsyncPackage, IVsRunningDocTableEvents
|
||||
{
|
||||
public const string PackageGuidString = "1e02d547-499b-41ed-a25e-b356a9687e31";
|
||||
private const int DebounceMilliseconds = 200;
|
||||
private const int HeartbeatMilliseconds = 15000;
|
||||
private IVsRunningDocumentTable? runningDocuments;
|
||||
private IVsSolution? solution;
|
||||
private SnapshotPublisher? publisher;
|
||||
private Timer? debounceTimer;
|
||||
private Timer? heartbeatTimer;
|
||||
private uint eventsCookie;
|
||||
private bool disposing;
|
||||
|
||||
protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
|
||||
{
|
||||
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
|
||||
runningDocuments = await GetServiceAsync(typeof(SVsRunningDocumentTable)) as IVsRunningDocumentTable;
|
||||
solution = await GetServiceAsync(typeof(SVsSolution)) as IVsSolution;
|
||||
if (runningDocuments == null) throw new InvalidOperationException("Visual Studio Running Document Table is unavailable.");
|
||||
|
||||
publisher = new SnapshotPublisher();
|
||||
ErrorHandler.ThrowOnFailure(runningDocuments.AdviseRunningDocTableEvents(this, out eventsCookie));
|
||||
debounceTimer = new Timer(_ => JoinableTaskFactory.RunAsync(PublishAsync), null, Timeout.Infinite, Timeout.Infinite);
|
||||
heartbeatTimer = new Timer(_ => JoinableTaskFactory.RunAsync(PublishAsync), null, HeartbeatMilliseconds, HeartbeatMilliseconds);
|
||||
AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
|
||||
await PublishAsync();
|
||||
}
|
||||
|
||||
private void SchedulePublish()
|
||||
{
|
||||
if (!disposing) debounceTimer?.Change(DebounceMilliseconds, Timeout.Infinite);
|
||||
}
|
||||
|
||||
private async Task PublishAsync()
|
||||
{
|
||||
if (disposing || publisher == null || runningDocuments == null) return;
|
||||
await JoinableTaskFactory.SwitchToMainThreadAsync();
|
||||
var documents = EnumerateDocuments(runningDocuments);
|
||||
var workspace = ReadSolutionPath(solution);
|
||||
var snapshot = SnapshotModel.Create(publisher.InstanceId, Process.GetCurrentProcess().Id, workspace, documents, DateTimeOffset.UtcNow);
|
||||
try { publisher.Publish(snapshot); } catch (Exception error) { Trace.WriteLine($"DSH Open File Bridge publish failed: {error}"); }
|
||||
}
|
||||
|
||||
private static IReadOnlyList<OpenDocument> EnumerateDocuments(IVsRunningDocumentTable table)
|
||||
{
|
||||
ThreadHelper.ThrowIfNotOnUIThread();
|
||||
ErrorHandler.ThrowOnFailure(table.GetRunningDocumentsEnum(out var iterator));
|
||||
var result = new List<OpenDocument>();
|
||||
var cookies = new uint[1];
|
||||
while (iterator.Next(1, cookies, out var fetched) == VSConstants.S_OK && fetched == 1)
|
||||
{
|
||||
IntPtr docData = IntPtr.Zero;
|
||||
try
|
||||
{
|
||||
ErrorHandler.ThrowOnFailure(table.GetDocumentInfo(cookies[0], out _, out _, out _, out var moniker, out _, out _, out docData));
|
||||
if (string.IsNullOrWhiteSpace(moniker) || !Path.IsPathRooted(moniker) || !File.Exists(moniker)) continue;
|
||||
var dirty = false;
|
||||
if (docData != IntPtr.Zero)
|
||||
{
|
||||
var value = Marshal.GetObjectForIUnknown(docData);
|
||||
if (value is IVsPersistDocData persist && ErrorHandler.Succeeded(persist.IsDocDataDirty(out var isDirty))) dirty = isDirty != 0;
|
||||
}
|
||||
result.Add(new OpenDocument { Path = Path.GetFullPath(moniker), Label = Path.GetFileName(moniker), Dirty = dirty });
|
||||
}
|
||||
catch (Exception error) { Trace.WriteLine($"DSH Open File Bridge skipped one document: {error.Message}"); }
|
||||
finally { if (docData != IntPtr.Zero) Marshal.Release(docData); }
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string? ReadSolutionPath(IVsSolution? solutionService)
|
||||
{
|
||||
ThreadHelper.ThrowIfNotOnUIThread();
|
||||
if (solutionService == null) return null;
|
||||
return ErrorHandler.Succeeded(solutionService.GetSolutionInfo(out _, out var solutionFile, out _)) && !string.IsNullOrWhiteSpace(solutionFile)
|
||||
? solutionFile : null;
|
||||
}
|
||||
|
||||
private void OnProcessExit(object sender, EventArgs e) => publisher?.Dispose();
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
this.disposing = true;
|
||||
if (disposing)
|
||||
{
|
||||
AppDomain.CurrentDomain.ProcessExit -= OnProcessExit;
|
||||
debounceTimer?.Dispose();
|
||||
heartbeatTimer?.Dispose();
|
||||
if (runningDocuments != null && eventsCookie != 0)
|
||||
{
|
||||
JoinableTaskFactory.Run(async () => {
|
||||
await JoinableTaskFactory.SwitchToMainThreadAsync();
|
||||
runningDocuments.UnadviseRunningDocTableEvents(eventsCookie);
|
||||
});
|
||||
}
|
||||
publisher?.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
public int OnAfterFirstDocumentLock(uint docCookie, uint lockType, uint readLocks, uint editLocks) { SchedulePublish(); return VSConstants.S_OK; }
|
||||
public int OnBeforeLastDocumentUnlock(uint docCookie, uint lockType, uint readLocks, uint editLocks) { SchedulePublish(); return VSConstants.S_OK; }
|
||||
public int OnAfterSave(uint docCookie) { SchedulePublish(); return VSConstants.S_OK; }
|
||||
public int OnAfterAttributeChange(uint docCookie, uint attributes) { SchedulePublish(); return VSConstants.S_OK; }
|
||||
public int OnBeforeDocumentWindowShow(uint docCookie, int firstShow, IVsWindowFrame frame) { SchedulePublish(); return VSConstants.S_OK; }
|
||||
public int OnAfterDocumentWindowHide(uint docCookie, IVsWindowFrame frame) { SchedulePublish(); return VSConstants.S_OK; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user