From 276312348a21d8b5555bdc466152504c3aaef67c Mon Sep 17 00:00:00 2001
From: Alistair Chapman <alistair@cakebuild.net>
Date: Mon, 12 Dec 2016 01:04:18 +1000
Subject: [PATCH] Add support for drag-and-drop with cake and dll files

---
 src/Cake.VisualStudio.csproj                |   3 +
 src/Editor/CakeDropHandler.cs               |  31 ++++++
 src/Editor/CakeScriptDropHandler.cs         | 100 ++++++++++++++++++++
 src/Editor/CakeScriptDropHandlerProvider.cs |  42 ++++++++
 4 files changed, 176 insertions(+)
 create mode 100644 src/Editor/CakeDropHandler.cs
 create mode 100644 src/Editor/CakeScriptDropHandler.cs
 create mode 100644 src/Editor/CakeScriptDropHandlerProvider.cs

diff --git a/src/Cake.VisualStudio.csproj b/src/Cake.VisualStudio.csproj
index fc47377..84dac9f 100644
--- a/src/Cake.VisualStudio.csproj
+++ b/src/Cake.VisualStudio.csproj
@@ -72,6 +72,9 @@
     <Compile Include="Editor\SmartIndentProvider.cs" />
     <Compile Include="Editor\SmartIndent.cs" />
     <Compile Include="ContentType\CakeLanguageService.cs" />
+    <Compile Include="Editor\CakeDropHandler.cs" />
+    <Compile Include="Editor\CakeScriptDropHandler.cs" />
+    <Compile Include="Editor\CakeScriptDropHandlerProvider.cs" />
     <Compile Include="Helpers\Constants.cs" />
     <Compile Include="Helpers\Extensions.cs" />
     <Compile Include="Helpers\PathHelpers.cs" />
diff --git a/src/Editor/CakeDropHandler.cs b/src/Editor/CakeDropHandler.cs
new file mode 100644
index 0000000..588c58a
--- /dev/null
+++ b/src/Editor/CakeDropHandler.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.Text.Editor.DragDrop;
+
+namespace Cake.VisualStudio.Editor
+{
+    internal abstract class CakeDropHandler : IDropHandler
+    {
+        public DragDropPointerEffects HandleDragStarted(DragDropInfo dragDropInfo)
+        {
+            return DragDropPointerEffects.All;
+        }
+
+        public DragDropPointerEffects HandleDraggingOver(DragDropInfo dragDropInfo)
+        {
+            return DragDropPointerEffects.All;
+        }
+
+        public abstract DragDropPointerEffects HandleDataDropped(DragDropInfo dragDropInfo);
+
+        public abstract bool IsDropEnabled(DragDropInfo dragDropInfo);
+
+        public void HandleDragCanceled()
+        {
+            
+        }
+    }
+}
diff --git a/src/Editor/CakeScriptDropHandler.cs b/src/Editor/CakeScriptDropHandler.cs
new file mode 100644
index 0000000..b1ad0fa
--- /dev/null
+++ b/src/Editor/CakeScriptDropHandler.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using Microsoft.VisualStudio.Shell;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Editor.DragDrop;
+
+namespace Cake.VisualStudio.Editor
+{
+    internal class CakeScriptDropHandler : CakeDropHandler
+    {
+        private readonly FileInfo _scriptFile;
+        private string _targetFileName;
+        private readonly IWpfTextView _textView;
+        private ITextDocument _document;
+        private readonly string[] _supportedFileExtensions = new[] {".cake", ".dll"};
+
+        public CakeScriptDropHandler(IWpfTextView textView, ITextDocument currentDocument)
+        {
+            _textView = textView;
+            _scriptFile = new FileInfo(currentDocument.FilePath);
+            _document = currentDocument;
+        }
+
+        public override DragDropPointerEffects HandleDataDropped(DragDropInfo dragDropInfo)
+        {
+            try
+            {
+                var relativePath = PackageUtilities.MakeRelative(_scriptFile.FullName, _targetFileName)
+                    .Replace("\\", "/");
+                string insertString = null;
+                switch (Path.GetExtension(relativePath))
+                {
+                    case ".cake":
+                        insertString = $"#load \"{relativePath}\"";
+                        break;
+                    case ".dll":
+                        insertString = $"#r \"{relativePath}\"";
+                        break;
+                }
+                
+                using (var edit = _textView.TextBuffer.CreateEdit())
+                {
+                    edit.Insert(GetInsertPosition(), insertString + Environment.NewLine);
+                    edit.Apply();
+                }
+            }
+            catch (Exception)
+            {
+                // ignored
+            }
+
+            return DragDropPointerEffects.Copy;
+        }
+
+        private int GetInsertPosition()
+        {
+            // need to improve logic here to find the first non-#load'ing line in the document.
+            return 0;
+        }
+
+        public override bool IsDropEnabled(DragDropInfo dragDropInfo)
+        {
+            _targetFileName = GetScriptFileName(dragDropInfo);
+            if (_targetFileName == null) return false;
+            var ext = Path.GetExtension(_targetFileName);
+            return _supportedFileExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase) &&
+                   (File.Exists(_targetFileName) || Directory.Exists(_targetFileName));
+        }
+
+        private static string GetScriptFileName(DragDropInfo dragDropInfo)
+        {
+            var data = new DataObject(dragDropInfo.Data);
+
+            if (dragDropInfo.Data.GetDataPresent("FileDrop"))
+            {
+                var files = data.GetFileDropList();
+
+                if (files.Count == 1)
+                {
+                    return files[0];
+                }
+            }
+            else if (dragDropInfo.Data.GetDataPresent("CF_VSSTGPROJECTITEMS") || dragDropInfo.Data.GetDataPresent("CF_VSREFPROJECTITEMS"))
+            {
+                return data.GetText();
+            }
+            else if (dragDropInfo.Data.GetDataPresent("MultiURL"))
+            {
+                return data.GetText();
+            }
+            return null;
+        }
+    }
+}
diff --git a/src/Editor/CakeScriptDropHandlerProvider.cs b/src/Editor/CakeScriptDropHandlerProvider.cs
new file mode 100644
index 0000000..c1ef6f1
--- /dev/null
+++ b/src/Editor/CakeScriptDropHandlerProvider.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Cake.VisualStudio.Classifier.Languages;
+using Cake.VisualStudio.Helpers;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Editor.DragDrop;
+using Microsoft.VisualStudio.Utilities;
+
+namespace Cake.VisualStudio.Editor
+{
+    [Export(typeof(IDropHandlerProvider))]
+    [DropFormat("CF_VSSTGPROJECTITEMS")]
+    [DropFormat("CF_VSREFPROJECTITEMS")]
+    [DropFormat("FileDrop")]
+    [Name("CakeDropHandler")]
+    [ContentType(Constants.CakeContentType)]
+    [Order(Before = "DefaultFileDropHandler")]
+    class CakeScriptDropHandlerProvider : IDropHandlerProvider
+    {
+        [Import]
+        ITextDocumentFactoryService TextDocumentFactoryService { get; set; }
+
+        public IDropHandler GetAssociatedDropHandler(IWpfTextView wpfTextView)
+        {
+            ITextDocument document;
+
+            if (TextDocumentFactoryService.TryGetTextDocument(wpfTextView.TextBuffer, out document))
+            {
+                return
+                    wpfTextView.Properties.GetOrCreateSingletonProperty(
+                        () => new CakeScriptDropHandler(wpfTextView, document));
+            }
+
+            return null;
+        }
+    }
+}