diff --git a/src/InteractiveWindow/EditorTest/InteractiveWindowEditorsFactoryService.cs b/src/InteractiveWindow/EditorTest/InteractiveWindowEditorsFactoryService.cs new file mode 100644 index 0000000000000000000000000000000000000000..17dbfe73bb7aba5baa8b7f702e7c5fe48f3abeb4 --- /dev/null +++ b/src/InteractiveWindow/EditorTest/InteractiveWindowEditorsFactoryService.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.ComponentModel.Composition; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Utilities; +using Roslyn.Editor.InteractiveWindow; + +namespace Roslyn.InteractiveWindow.UnitTests +{ + [Export(typeof(IInteractiveWindowEditorFactoryService))] + internal class InteractiveWindowEditorsFactoryService : IInteractiveWindowEditorFactoryService + { + private readonly ITextBufferFactoryService _textBufferFactoryService; + private readonly ITextEditorFactoryService _textEditorFactoryService; + + [ImportingConstructor] + public InteractiveWindowEditorsFactoryService(ITextBufferFactoryService textBufferFactoryService, ITextEditorFactoryService textEditorFactoryService) + { + _textBufferFactoryService = textBufferFactoryService; + _textEditorFactoryService = textEditorFactoryService; + } + + IWpfTextView IInteractiveWindowEditorFactoryService.CreateTextView(IInteractiveWindow window, ITextBuffer buffer, ITextViewRoleSet roles) + { + var textView = _textEditorFactoryService.CreateTextView(buffer, roles); + return _textEditorFactoryService.CreateTextViewHost(textView, false).TextView; + } + + ITextBuffer IInteractiveWindowEditorFactoryService.CreateAndActivateBuffer(IInteractiveWindow window, IContentType contentType) + { + return _textBufferFactoryService.CreateTextBuffer(contentType); + } + } +} diff --git a/src/InteractiveWindow/EditorTest/InteractiveWindowTest.csproj b/src/InteractiveWindow/EditorTest/InteractiveWindowTest.csproj new file mode 100644 index 0000000000000000000000000000000000000000..0d7a5fe80d9093be31f92335c92819e784bc2ee4 --- /dev/null +++ b/src/InteractiveWindow/EditorTest/InteractiveWindowTest.csproj @@ -0,0 +1,94 @@ + + + + + + + + true + Debug + x86 + {7F3CB45E-4993-4FA4-8D6A-C2DFFED2DFC3} + Library + Roslyn.InteractiveWindow.UnitTests + Roslyn.InteractiveWindow.UnitTests + x86 + ..\..\..\ + true + + + + + + + + + {01E9BD68-0339-4A13-B42F-A3CA84D164F3} + InteractiveWindow + + + + + + + + + + + + + + + + + + + $(DevEnvDir)\PrivateAssemblies\Microsft.VisualStudio.Platform.VSEditor.Interop.dll + + + + False + ..\..\..\packages\System.Collections.Immutable.1.1.33-beta\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll + + + + + + + ..\..\..\packages\xunit.1.9.2\lib\net20\xunit.dll + + + False + ..\..\..\packages\BasicUndo.0.9.2\lib\net45\BasicUndo.dll + + + + + AssertEx.cs + + + DiffUtil.cs + + + WaitHelper.cs + + + + + + + + + + + + + + + + + + + + + diff --git a/src/InteractiveWindow/EditorTest/InteractiveWindowTestHost.cs b/src/InteractiveWindow/EditorTest/InteractiveWindowTestHost.cs new file mode 100644 index 0000000000000000000000000000000000000000..a8c4f3bd0d7ee76e6e850e55e1313d41421019f0 --- /dev/null +++ b/src/InteractiveWindow/EditorTest/InteractiveWindowTestHost.cs @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.ComponentModel.Composition.Hosting; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Windows.Threading; +using Microsoft.VisualStudio.Language.Intellisense.Utilities; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Utilities; +using Roslyn.Test.Utilities; + +namespace Roslyn.Editor.InteractiveWindow.UnitTests +{ + public class InteractiveWindowTestHost : IDisposable + { + private readonly IInteractiveWindow _window; + private readonly CompositionContainer _exportProvider; + + public InteractiveWindowTestHost() + { + SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext()); + + var types = new[] { typeof(TestInteractiveEngine), typeof(InteractiveWindow) }.Concat(GetVisualStudioTypes()); + _exportProvider = new CompositionContainer( + new AggregateCatalog(types.Select(t => new AssemblyCatalog(t.Assembly))), + CompositionOptions.DisableSilentRejection | CompositionOptions.IsThreadSafe); + + var contentTypeRegistryService = _exportProvider.GetExport().Value; + _window = _exportProvider.GetExport().Value.CreateWindow(new TestInteractiveEngine(contentTypeRegistryService)); + + _window.InitializeAsync().PumpingWait(); + } + + public CompositionContainer ExportProvider + { + get + { + return _exportProvider; + } + } + + public static Type[] GetVisualStudioTypes() + { + var types = new[] + { + // EDITOR + + // Microsoft.VisualStudio.Platform.VSEditor.dll: + typeof(Microsoft.VisualStudio.Platform.VSEditor.EventArgsHelper), + + // Microsoft.VisualStudio.Text.Logic.dll: + // Must include this because several editor options are actually stored as exported information + // on this DLL. Including most importantly, the tab size information. + typeof(Microsoft.VisualStudio.Text.Editor.DefaultOptions), + + // Microsoft.VisualStudio.Text.UI.dll: + // Include this DLL to get several more EditorOptions including WordWrapStyle. + typeof(Microsoft.VisualStudio.Text.Editor.WordWrapStyle), + + // Microsoft.VisualStudio.Text.UI.Wpf.dll: + // Include this DLL to get more EditorOptions values. + typeof(Microsoft.VisualStudio.Text.Editor.HighlightCurrentLineOption), + + // BasicUndo.dll: + // Include this DLL to satisfy ITextUndoHistoryRegistry + typeof(BasicUndo.IBasicUndoHistory), + + // Microsoft.VisualStudio.Language.StandardClassification.dll: + typeof(Microsoft.VisualStudio.Language.StandardClassification.PredefinedClassificationTypeNames) + }; + + return types; + } + + internal IInteractiveWindow Window + { + get + { + return _window; + } + } + + public void Dispose() + { + if (_window != null) + { + // close interactive host process: + var engine = _window.Evaluator; + if (engine != null) + { + engine.Dispose(); + } + + // dispose buffer: + _window.Dispose(); + } + } + } +} diff --git a/src/InteractiveWindow/EditorTest/InteractiveWindowTests.cs b/src/InteractiveWindow/EditorTest/InteractiveWindowTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..f21f6250c1e8939cb0fe015931c0f65672a806da --- /dev/null +++ b/src/InteractiveWindow/EditorTest/InteractiveWindowTests.cs @@ -0,0 +1,174 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.VisualStudio.Text; +using Moq; +using Roslyn.Editor.InteractiveWindow.Commands; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Roslyn.Editor.InteractiveWindow.UnitTests +{ + public class InteractiveWindowTests : IDisposable + { + #region Helpers + + private InteractiveWindowTestHost _testHost; + + public InteractiveWindowTests() + { + _testHost = new InteractiveWindowTestHost(); + } + + public void Dispose() + { + _testHost.Dispose(); + } + + public IInteractiveWindow Window + { + get + { + return _testHost.Window; + } + } + + public static IEnumerable MockCommands(params string[] commandNames) + { + foreach (var name in commandNames) + { + var mock = new Mock(); + mock.Setup(m => m.Name).Returns(name); + yield return mock.Object; + } + } + + public static ITextSnapshot MockSnapshot(string content) + { + var snapshotMock = new Mock(); + snapshotMock.Setup(m => m[It.IsAny()]).Returns(index => content[index]); + snapshotMock.Setup(m => m.Length).Returns(content.Length); + snapshotMock.Setup(m => m.GetText()).Returns(content); + snapshotMock.Setup(m => m.GetText(It.IsAny(), It.IsAny())).Returns((start, length) => content.Substring(start, length)); + snapshotMock.Setup(m => m.GetText(It.IsAny())).Returns(span => content.Substring(span.Start, span.Length)); + return snapshotMock.Object; + } + + #endregion + + [Fact] + public void InteractiveWindow__CommandParsing() + { + var commandList = MockCommands("foo", "bar", "bz", "command1").ToArray(); + var commands = new Commands.Commands(null, "%", commandList); + AssertEx.Equal(commands.GetCommands(), commandList); + + var cmdBar = commandList[1]; + Assert.Equal("bar", cmdBar.Name); + + Assert.Equal("%", commands.CommandPrefix); + commands.CommandPrefix = "#"; + Assert.Equal("#", commands.CommandPrefix); + + //// 111111 + //// 0123456789012345 + var s1 = MockSnapshot("#bar arg1 arg2 "); + + SnapshotSpan prefixSpan, commandSpan, argsSpan; + IInteractiveWindowCommand cmd; + + cmd = commands.TryParseCommand(new SnapshotSpan(s1, Span.FromBounds(0, 0)), out prefixSpan, out commandSpan, out argsSpan); + Assert.Null(cmd); + + cmd = commands.TryParseCommand(new SnapshotSpan(s1, Span.FromBounds(0, 1)), out prefixSpan, out commandSpan, out argsSpan); + Assert.Null(cmd); + + cmd = commands.TryParseCommand(new SnapshotSpan(s1, Span.FromBounds(0, 2)), out prefixSpan, out commandSpan, out argsSpan); + Assert.Null(cmd); + Assert.Equal(0, prefixSpan.Start); + Assert.Equal(1, prefixSpan.End); + Assert.Equal(1, commandSpan.Start); + Assert.Equal(2, commandSpan.End); + Assert.Equal(2, argsSpan.Start); + Assert.Equal(2, argsSpan.End); + + cmd = commands.TryParseCommand(new SnapshotSpan(s1, Span.FromBounds(0, 3)), out prefixSpan, out commandSpan, out argsSpan); + Assert.Null(cmd); + Assert.Equal(0, prefixSpan.Start); + Assert.Equal(1, prefixSpan.End); + Assert.Equal(1, commandSpan.Start); + Assert.Equal(3, commandSpan.End); + Assert.Equal(3, argsSpan.Start); + Assert.Equal(3, argsSpan.End); + + cmd = commands.TryParseCommand(new SnapshotSpan(s1, Span.FromBounds(0, 4)), out prefixSpan, out commandSpan, out argsSpan); + Assert.Equal(cmdBar, cmd); + Assert.Equal(0, prefixSpan.Start); + Assert.Equal(1, prefixSpan.End); + Assert.Equal(1, commandSpan.Start); + Assert.Equal(4, commandSpan.End); + Assert.Equal(4, argsSpan.Start); + Assert.Equal(4, argsSpan.End); + + cmd = commands.TryParseCommand(new SnapshotSpan(s1, Span.FromBounds(0, 5)), out prefixSpan, out commandSpan, out argsSpan); + Assert.Equal(cmdBar, cmd); + Assert.Equal(0, prefixSpan.Start); + Assert.Equal(1, prefixSpan.End); + Assert.Equal(1, commandSpan.Start); + Assert.Equal(4, commandSpan.End); + Assert.Equal(5, argsSpan.Start); + Assert.Equal(5, argsSpan.End); + + cmd = commands.TryParseCommand(s1.GetExtent(), out prefixSpan, out commandSpan, out argsSpan); + Assert.Equal(cmdBar, cmd); + Assert.Equal(0, prefixSpan.Start); + Assert.Equal(1, prefixSpan.End); + Assert.Equal(1, commandSpan.Start); + Assert.Equal(4, commandSpan.End); + Assert.Equal(5, argsSpan.Start); + Assert.Equal(14, argsSpan.End); + + //// + //// 0123456789 + var s2 = MockSnapshot(" #bar "); + cmd = commands.TryParseCommand(s2.GetExtent(), out prefixSpan, out commandSpan, out argsSpan); + Assert.Equal(cmdBar, cmd); + Assert.Equal(2, prefixSpan.Start); + Assert.Equal(3, prefixSpan.End); + Assert.Equal(3, commandSpan.Start); + Assert.Equal(6, commandSpan.End); + Assert.Equal(9, argsSpan.Start); + Assert.Equal(9, argsSpan.End); + + //// 111111 + //// 0123456789012345 + var s3 = MockSnapshot(" # bar args"); + cmd = commands.TryParseCommand(s3.GetExtent(), out prefixSpan, out commandSpan, out argsSpan); + Assert.Equal(cmdBar, cmd); + Assert.Equal(2, prefixSpan.Start); + Assert.Equal(3, prefixSpan.End); + Assert.Equal(6, commandSpan.Start); + Assert.Equal(9, commandSpan.End); + Assert.Equal(11, argsSpan.Start); + Assert.Equal(15, argsSpan.End); + } + + [Fact] + public void InteractiveWindow_GetCommands() + { + var interactiveCommands = new InteractiveCommandsFactory(null, null).CreateInteractiveCommands( + Window, + "#", + _testHost.ExportProvider.GetExports().Select(x => x.Value).ToArray()); + + var commands = interactiveCommands.GetCommands(); + + Assert.NotEmpty(commands); + Assert.NotNull(commands.Where(n => n.Name == "cls").SingleOrDefault()); + Assert.NotNull(commands.Where(n => n.Name == "help").SingleOrDefault()); + Assert.NotNull(commands.Where(n => n.Name == "reset").SingleOrDefault()); + } + } +} diff --git a/src/InteractiveWindow/EditorTest/TestContentTypeDefinition.cs b/src/InteractiveWindow/EditorTest/TestContentTypeDefinition.cs new file mode 100644 index 0000000000000000000000000000000000000000..087b31e3b23a3e2d2a8818c0132f4920c0cbe276 --- /dev/null +++ b/src/InteractiveWindow/EditorTest/TestContentTypeDefinition.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.ComponentModel.Composition; +using Microsoft.VisualStudio.Utilities; + +namespace Roslyn.Editor.InteractiveWindow.UnitTests +{ + public sealed class TestContentTypeDefinition + { + public const string ContentTypeName = "InteractiveWindowTest"; + + [Export] + [Name(ContentTypeName)] + [BaseDefinition("code")] + public static readonly ContentTypeDefinition Definition; + } +} diff --git a/src/InteractiveWindow/EditorTest/TestInteractiveEngine.cs b/src/InteractiveWindow/EditorTest/TestInteractiveEngine.cs new file mode 100644 index 0000000000000000000000000000000000000000..0cd7d539d74c7e823fc33b2e4333ee7761463959 --- /dev/null +++ b/src/InteractiveWindow/EditorTest/TestInteractiveEngine.cs @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Utilities; + +namespace Roslyn.Editor.InteractiveWindow.UnitTests +{ + public class TestInteractiveEngine : IInteractiveEvaluator + { + private readonly IContentType _contentType; + private IInteractiveWindow _currentWindow; + + public TestInteractiveEngine(IContentTypeRegistryService contentTypeRegistryService) + { + _contentType = contentTypeRegistryService.GetContentType(TestContentTypeDefinition.ContentTypeName); + } + + public IContentType ContentType + { + get { return _contentType; } + } + + public IInteractiveWindow CurrentWindow + { + get + { + return _currentWindow; + } + + set + { + _currentWindow = value; + } + } + + public void Dispose() + { + } + + public Task InitializeAsync() + { + return Task.FromResult(ExecutionResult.Success); + } + + public Task ResetAsync(bool initialize = true) + { + return Task.FromResult(ExecutionResult.Success); + } + + public bool CanExecuteText(string text) + { + return true; + } + + public Task ExecuteTextAsync(string text) + { + return Task.FromResult(ExecutionResult.Success); + } + + public string FormatClipboard() + { + return ""; + } + + public void AbortCommand() + { + } + + public string GetConfiguration() + { + return "config"; + } + + public string GetPrompt() + { + return "> "; + } + } +} diff --git a/src/InteractiveWindow/EditorTest/TestWaitIndicator.cs b/src/InteractiveWindow/EditorTest/TestWaitIndicator.cs new file mode 100644 index 0000000000000000000000000000000000000000..05d39af91ee4ce6f459fa17372a958151bd00f8f --- /dev/null +++ b/src/InteractiveWindow/EditorTest/TestWaitIndicator.cs @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.ComponentModel.Composition; +using System.Threading; +using Microsoft.VisualStudio.Language.Intellisense.Utilities; + +namespace Roslyn.Editor.InteractiveWindow.UnitTests +{ + [Export(typeof(IWaitIndicator))] + internal class TestWaitIndicator : IWaitIndicator + { + public IWaitContext StartWait(string title, string message, bool allowCancel) + { + return new WaitContext(); + } + + public WaitIndicatorResult Wait(string title, string message, bool allowCancel, Action action) + { + try + { + action(new WaitContext()); + } + catch + { + } + + return WaitIndicatorResult.Completed; + } + + private class WaitContext : IWaitContext + { + public bool AllowCancel + { + get + { + return false; + } + + set + { + } + } + + public CancellationToken CancellationToken + { + get + { + return CancellationToken.None; + } + } + + public string Message + { + get + { + return string.Empty; + } + + set + { + } + } + + public void UpdateProgress() + { + } + + public void Dispose() + { + } + } + } +} diff --git a/src/InteractiveWindow/EditorTest/packages.config b/src/InteractiveWindow/EditorTest/packages.config new file mode 100644 index 0000000000000000000000000000000000000000..c9c9f2d161f32685ab12a3ce8b53bb644872a641 --- /dev/null +++ b/src/InteractiveWindow/EditorTest/packages.config @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/InteractiveWindow/InteractiveWindow.sln b/src/InteractiveWindow/InteractiveWindow.sln new file mode 100644 index 0000000000000000000000000000000000000000..27cc46a3438861e9be2ead1a2ad2e7d9d09a706e --- /dev/null +++ b/src/InteractiveWindow/InteractiveWindow.sln @@ -0,0 +1,71 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.21803.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InteractiveWindow", "Editor\InteractiveWindow.csproj", "{01E9BD68-0339-4A13-B42F-A3CA84D164F3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InteractiveWindowTest", "EditorTest\InteractiveWindowTest.csproj", "{7F3CB45E-4993-4FA4-8D6A-C2DFFED2DFC3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VisualStudioInteractiveWindow", "VisualStudio\VisualStudioInteractiveWindow.csproj", "{20BB6FAC-44D2-4D76-ABFE-0C1E163A1A4F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|Mixed Platforms = Debug|Mixed Platforms + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|Mixed Platforms = Release|Mixed Platforms + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {01E9BD68-0339-4A13-B42F-A3CA84D164F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {01E9BD68-0339-4A13-B42F-A3CA84D164F3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {01E9BD68-0339-4A13-B42F-A3CA84D164F3}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {01E9BD68-0339-4A13-B42F-A3CA84D164F3}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {01E9BD68-0339-4A13-B42F-A3CA84D164F3}.Debug|x86.ActiveCfg = Debug|Any CPU + {01E9BD68-0339-4A13-B42F-A3CA84D164F3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {01E9BD68-0339-4A13-B42F-A3CA84D164F3}.Release|Any CPU.Build.0 = Release|Any CPU + {01E9BD68-0339-4A13-B42F-A3CA84D164F3}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {01E9BD68-0339-4A13-B42F-A3CA84D164F3}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {01E9BD68-0339-4A13-B42F-A3CA84D164F3}.Release|x86.ActiveCfg = Release|Any CPU + {7F3CB45E-4993-4FA4-8D6A-C2DFFED2DFC3}.Debug|Any CPU.ActiveCfg = Debug|x86 + {7F3CB45E-4993-4FA4-8D6A-C2DFFED2DFC3}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {7F3CB45E-4993-4FA4-8D6A-C2DFFED2DFC3}.Debug|Mixed Platforms.Build.0 = Debug|x86 + {7F3CB45E-4993-4FA4-8D6A-C2DFFED2DFC3}.Debug|x86.ActiveCfg = Debug|x86 + {7F3CB45E-4993-4FA4-8D6A-C2DFFED2DFC3}.Debug|x86.Build.0 = Debug|x86 + {7F3CB45E-4993-4FA4-8D6A-C2DFFED2DFC3}.Release|Any CPU.ActiveCfg = Release|x86 + {7F3CB45E-4993-4FA4-8D6A-C2DFFED2DFC3}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {7F3CB45E-4993-4FA4-8D6A-C2DFFED2DFC3}.Release|Mixed Platforms.Build.0 = Release|x86 + {7F3CB45E-4993-4FA4-8D6A-C2DFFED2DFC3}.Release|x86.ActiveCfg = Release|x86 + {7F3CB45E-4993-4FA4-8D6A-C2DFFED2DFC3}.Release|x86.Build.0 = Release|x86 + {20BB6FAC-44D2-4D76-ABFE-0C1E163A1A4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {20BB6FAC-44D2-4D76-ABFE-0C1E163A1A4F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {20BB6FAC-44D2-4D76-ABFE-0C1E163A1A4F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {20BB6FAC-44D2-4D76-ABFE-0C1E163A1A4F}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {20BB6FAC-44D2-4D76-ABFE-0C1E163A1A4F}.Debug|x86.ActiveCfg = Debug|Any CPU + {20BB6FAC-44D2-4D76-ABFE-0C1E163A1A4F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {20BB6FAC-44D2-4D76-ABFE-0C1E163A1A4F}.Release|Any CPU.Build.0 = Release|Any CPU + {20BB6FAC-44D2-4D76-ABFE-0C1E163A1A4F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {20BB6FAC-44D2-4D76-ABFE-0C1E163A1A4F}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {20BB6FAC-44D2-4D76-ABFE-0C1E163A1A4F}.Release|x86.ActiveCfg = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(TeamFoundationVersionControl) = preSolution + SccNumberOfProjects = 4 + SccEnterpriseProvider = {4CA58AB2-18FA-4F8D-95D4-32DDF27D184C} + SccTeamFoundationServer = http://vstfdevdiv:8080/devdiv2 + SccLocalPath0 = . + SccProjectUniqueName1 = Editor\\InteractiveWindow.csproj + SccProjectName1 = Editor + SccLocalPath1 = Editor + SccProjectUniqueName2 = EditorTest\\InteractiveWindowTest.csproj + SccProjectName2 = EditorTest + SccLocalPath2 = EditorTest + SccProjectUniqueName3 = VisualStudio\\VisualStudioInteractiveWindow.csproj + SccProjectName3 = VisualStudio + SccLocalPath3 = VisualStudio + EndGlobalSection +EndGlobal diff --git a/src/InteractiveWindow/VisualStudio/CommandIds.cs b/src/InteractiveWindow/VisualStudio/CommandIds.cs new file mode 100644 index 0000000000000000000000000000000000000000..8f11e35a740c90dd8d21b3e0803988ebeb32d548 --- /dev/null +++ b/src/InteractiveWindow/VisualStudio/CommandIds.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Roslyn.VisualStudio.InteractiveWindow +{ + public enum CommandIds : uint + { + // TODO (crwilcox): should all of these be in the editoroperations? + SmartExecute = 0x103, + AbortExecution = 0x104, + Reset = 0x105, + HistoryNext = 0x0106, + HistoryPrevious = 0x0107, + ClearScreen = 0x0108, + BreakLine = 0x0109, + SearchHistoryNext = 0x010A, + SearchHistoryPrevious = 0x010B, + ExecuteInInteractiveWindow = 0x010C, + CopyToInteractiveWindow = 0x010D, + } +} diff --git a/src/InteractiveWindow/VisualStudio/Guids.cs b/src/InteractiveWindow/VisualStudio/Guids.cs new file mode 100644 index 0000000000000000000000000000000000000000..eb581071f34e1042503b5bd55774b39d32980a4f --- /dev/null +++ b/src/InteractiveWindow/VisualStudio/Guids.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Roslyn.VisualStudio.InteractiveWindow +{ + public static class Guids + { + // vsct guids: + // This GUID identifies the VsInteractiveWindow type. We need to pass it to VS in a string form. + public const string InteractiveToolWindowIdString = "2D0A56AA-9527-4B78-B6E6-EBE6E05DA749"; + public const string InteractiveWindowPackageIdString = "F5199A4E-6A60-4F79-82E9-FC92A41C4610"; + public const string InteractiveCommandSetIdString = "00B8868B-F9F5-4970-A048-410B05508506"; + + public static readonly Guid InteractiveToolWindowId = new Guid(InteractiveToolWindowIdString); + public static readonly Guid InteractiveWindowPackageId = new Guid(InteractiveWindowPackageIdString); + public static readonly Guid InteractiveCommandSetId = new Guid(InteractiveCommandSetIdString); + } +} diff --git a/src/InteractiveWindow/VisualStudio/IVsInteractiveWindow.cs b/src/InteractiveWindow/VisualStudio/IVsInteractiveWindow.cs new file mode 100644 index 0000000000000000000000000000000000000000..7e4e49bbbf1881b05482dfe5254e62d45894b099 --- /dev/null +++ b/src/InteractiveWindow/VisualStudio/IVsInteractiveWindow.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Roslyn.Editor.InteractiveWindow; + +namespace Roslyn.VisualStudio.InteractiveWindow +{ + /// + /// Provides access to an interactive window being hosted inside of Visual Studio's process using the + /// default tool window. + /// + /// These tool windows are created using ProvideInteractiveWindowAttribute which provides the normal + /// tool window registration options. Instances of the tool window are then created using + /// IVsInteractiveWindowFactory when VS calls on your packages IVsToolWindowFactory.CreateToolWindow + /// method. + /// + public interface IVsInteractiveWindow + { + /// + /// Gets the interactive window instance. + /// + IInteractiveWindow InteractiveWindow { get; } + + /// + /// Shows the window. + /// + void Show(bool focus); + + /// + /// Configures the window for the specified VS language service guid language preferences. + /// + /// Also installs a language appropriate command filter if one is exported via IVsInteractiveWindowOleCommandTargetProvider. + /// + void SetLanguage(Guid languageServiceGuid); + } +} diff --git a/src/InteractiveWindow/VisualStudio/IVsInteractiveWindowEditorsFactoryService.cs b/src/InteractiveWindow/VisualStudio/IVsInteractiveWindowEditorsFactoryService.cs new file mode 100644 index 0000000000000000000000000000000000000000..11f1323d5e37a96911f24f1cca78ee14c8a38bf0 --- /dev/null +++ b/src/InteractiveWindow/VisualStudio/IVsInteractiveWindowEditorsFactoryService.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.VisualStudio.Text.Editor; +using Roslyn.Editor.InteractiveWindow; + +namespace Microsoft.VisualStudio +{ + /// + /// Provides access to information and settings for an interactive window created inside of Visual Studio. + /// + public interface IVsInteractiveWindowEditorsFactoryService + { + /// + /// Gets the text view host for the given IInteractiveWindow instance. + /// + /// + /// + IWpfTextViewHost GetTextViewHost(IInteractiveWindow window); + + /// + /// Configures the window for the specified VS language service guid language preferences. + /// + /// Also installs a language appropriate command filter if one is exported via IVsInteractiveWindowOleCommandTargetProvider. + /// + void SetLanguage(IInteractiveWindow window, Guid languageServiceGuid); + } +} diff --git a/src/InteractiveWindow/VisualStudio/IVsInteractiveWindowFactory.cs b/src/InteractiveWindow/VisualStudio/IVsInteractiveWindowFactory.cs new file mode 100644 index 0000000000000000000000000000000000000000..fc007ce45738d32c0131dc5ab09ea42132e4a9cf --- /dev/null +++ b/src/InteractiveWindow/VisualStudio/IVsInteractiveWindowFactory.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Roslyn.Editor.InteractiveWindow; + +namespace Roslyn.VisualStudio.InteractiveWindow +{ + public interface IVsInteractiveWindowFactory + { + IVsInteractiveWindow Create(Guid providerId, int instanceId, string title, IInteractiveEvaluator evaluator); + } +} diff --git a/src/InteractiveWindow/VisualStudio/IVsInteractiveWindowOleCommandTargetProvider.cs b/src/InteractiveWindow/VisualStudio/IVsInteractiveWindowOleCommandTargetProvider.cs new file mode 100644 index 0000000000000000000000000000000000000000..7e70b6139b5928b3967a0e1d95536926255ea769 --- /dev/null +++ b/src/InteractiveWindow/VisualStudio/IVsInteractiveWindowOleCommandTargetProvider.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.VisualStudio.OLE.Interop; +using Microsoft.VisualStudio.Text.Editor; + +namespace Roslyn.VisualStudio.InteractiveWindow +{ + /// + /// The implementer is given a chance to attach a command filter that routes language services + /// commands into the Interactive Window command filter chain. + /// + public interface IVsInteractiveWindowOleCommandTargetProvider + { + IOleCommandTarget GetCommandTarget(IWpfTextView textView, IOleCommandTarget nextTarget); + } +} diff --git a/src/InteractiveWindow/VisualStudio/InteractiveWindow.vsct b/src/InteractiveWindow/VisualStudio/InteractiveWindow.vsct new file mode 100644 index 0000000000000000000000000000000000000000..03eb09bb9dc76ed5694a82917ee8854cfb5655dd --- /dev/null +++ b/src/InteractiveWindow/VisualStudio/InteractiveWindow.vsct @@ -0,0 +1,314 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Interactive Console + Interactive Console + + + + + + DefaultDocked + NoToolbarClose + AlwaysCreate + TextChanges + + Interactive Window + Interactive Window + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/InteractiveWindow/VisualStudio/InteractiveWindowPackage.cs b/src/InteractiveWindow/VisualStudio/InteractiveWindowPackage.cs new file mode 100644 index 0000000000000000000000000000000000000000..522786886ad625b3bb340a8138a0e523062e0086 --- /dev/null +++ b/src/InteractiveWindow/VisualStudio/InteractiveWindowPackage.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.ComponentModel; +using System.Runtime.InteropServices; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Roslyn.Utilities; + +namespace Roslyn.VisualStudio.InteractiveWindow +{ + /// + /// This is the class that implements the package exposed by this assembly. + /// + /// The minimum requirement for a class to be considered a valid package for Visual Studio + /// is to implement the IVsPackage interface and register itself with the shell. + /// This package uses the helper classes defined inside the Managed Package Framework (MPF) + /// to do it: it derives from the Package class that provides the implementation of the + /// IVsPackage interface and uses the registration attributes defined in the framework to + /// register itself and its components with the shell. + /// + // This attribute tells the PkgDef creation utility (CreatePkgDef.exe) that this class is + // a package. + [PackageRegistration(UseManagedResourcesOnly = true)] + [Description("Visual Studio Interactive Window")] + [ProvideKeyBindingTable(Guids.InteractiveToolWindowIdString, 200)] // Resource ID: "Interactive Window" + [ProvideMenuResource("Menus.ctmenu", 1)] + [Guid(Guids.InteractiveWindowPackageIdString)] + internal sealed class InteractiveWindowPackage : Package + { + } +} diff --git a/src/InteractiveWindow/VisualStudio/MenuIds.cs b/src/InteractiveWindow/VisualStudio/MenuIds.cs new file mode 100644 index 0000000000000000000000000000000000000000..795217bc59e210e164ed517f415eaecaf72b87e8 --- /dev/null +++ b/src/InteractiveWindow/VisualStudio/MenuIds.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Microsoft.VisualStudio +{ + internal enum MenuIds : uint + { + InteractiveWindowToolbar = 0x2000, + InteractiveWindowContextMenu = 0x2100, + } +} diff --git a/src/InteractiveWindow/VisualStudio/Properties/AssemblyInfo.cs b/src/InteractiveWindow/VisualStudio/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..5ea21f55a92cb842e228bd924aaa910b64ed2b89 --- /dev/null +++ b/src/InteractiveWindow/VisualStudio/Properties/AssemblyInfo.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Visual Studio Interactive Window")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] + +[assembly: InternalsVisibleTo("AnalysisTest, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] diff --git a/src/InteractiveWindow/VisualStudio/ProvideInteractiveWindowAttribute.cs b/src/InteractiveWindow/VisualStudio/ProvideInteractiveWindowAttribute.cs new file mode 100644 index 0000000000000000000000000000000000000000..0c62cdd70f6fe4deedee2186b63d5c48101bff25 --- /dev/null +++ b/src/InteractiveWindow/VisualStudio/ProvideInteractiveWindowAttribute.cs @@ -0,0 +1,161 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Drawing; +using System.Globalization; +using Microsoft.VisualStudio.Shell; + +namespace Roslyn.VisualStudio.InteractiveWindow +{ + /// + /// This attribute declares that a package own an interactive window. Visual Studio uses this + /// information to handle the positioning and persistence of your window. The attributes on a + /// package do not control the behavior of the package, but they can be used by registration + /// tools to register the proper information with Visual Studio. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] + public sealed class ProvideInteractiveWindowAttribute : RegistrationAttribute + { + public ProvideInteractiveWindowAttribute(string guid) + { + this.Id = new Guid(guid); + } + + public Guid Id { get; private set; } + + public Rectangle Position { get; set; } + + /// + /// Default DockStyle for the ToolWindow + /// + public VsDockStyle Style { get; set; } + + /// + /// Default width of the ToolWindow when docked + /// + public int DockedWidth { get; set; } + + /// + /// Default height of the ToolWindow when docked + /// + public int DockedHeight { get; set; } + + /// + /// Default Orientation for the ToolWindow, relative to the window specified by the Window Property + /// + public ToolWindowOrientation Orientation { get; set; } + + /// + /// Default Window that the ToolWindow will be docked with + /// + public string Window { get; set; } + + /// + /// Set to true if you want a tool window that behaves and has a lifetime like a document. + /// The tool window will only be MDI or floating and will remain visible in its position across all layout changes + /// until manualy closed by the user at which point it will be destroyed. + /// This flag implies DontForceCreate and destructive multi instance. + /// + public bool DocumentLikeTool { get; set; } + + private string GetRegistryKeyName() + { + return "ToolWindows\\" + Id.ToString("B"); + } + + /// + /// Called to register this attribute with the given context. The context + /// contains the location where the registration information should be placed. + /// it also contains such as the type being registered, and path information. + /// + public override void Register(RegistrationContext context) + { + using (Key childKey = context.CreateKey(GetRegistryKeyName())) + { + // Package owning this tool window + childKey.SetValue(string.Empty, context.ComponentType.GUID.ToString("B")); + + if (Orientation != ToolWindowOrientation.none) + { + childKey.SetValue("Orientation", OrientationToString(Orientation)); + } + + if (Style != VsDockStyle.none) + { + childKey.SetValue("Style", StyleToString(Style)); + } + + if (!string.IsNullOrEmpty(Window)) + { + childKey.SetValue("Window", Window); + } + + if (Position.Width != 0 && Position.Height != 0) + { + string positionString = string.Format(CultureInfo.InvariantCulture, "{0}, {1}, {2}, {3}", + Position.Left, + Position.Top, + Position.Right, + Position.Bottom); + + childKey.SetValue("Float", positionString); + } + + if (DockedWidth > 0) + { + childKey.SetValue("DockedWidth", DockedWidth); + } + + if (DockedHeight > 0) + { + childKey.SetValue("DockedHeight", DockedHeight); + } + + if (DocumentLikeTool) + { + childKey.SetValue("DocumentLikeTool", 1); + } + } + } + + /// + /// Unregister this Tool Window. + /// + public override void Unregister(RegistrationContext context) + { + context.RemoveKey(GetRegistryKeyName()); + } + + private string StyleToString(VsDockStyle style) + { + switch (style) + { + case VsDockStyle.MDI: return "MDI"; + case VsDockStyle.Float: return "Float"; + case VsDockStyle.Linked: return "Linked"; + case VsDockStyle.Tabbed: return "Tabbed"; + case VsDockStyle.AlwaysFloat: return "AlwaysFloat"; + case VsDockStyle.none: return string.Empty; + default: + // TODO: error message + throw new ArgumentException("Style"); + } + } + + private string OrientationToString(ToolWindowOrientation position) + { + switch (position) + { + case ToolWindowOrientation.Top: return "Top"; + case ToolWindowOrientation.Left: return "Left"; + case ToolWindowOrientation.Right: return "Right"; + case ToolWindowOrientation.Bottom: return "Bottom"; + case ToolWindowOrientation.none: return string.Empty; + default: + // TODO: error message + throw new ArgumentException("Orientation"); + } + } + } +} + diff --git a/src/InteractiveWindow/VisualStudio/Resources/InteractiveToolbarImages.png b/src/InteractiveWindow/VisualStudio/Resources/InteractiveToolbarImages.png new file mode 100644 index 0000000000000000000000000000000000000000..d9d9ee596e30d145270e4985d7ab4f2130afe69e Binary files /dev/null and b/src/InteractiveWindow/VisualStudio/Resources/InteractiveToolbarImages.png differ diff --git a/src/InteractiveWindow/VisualStudio/Resources/Package.ico b/src/InteractiveWindow/VisualStudio/Resources/Package.ico new file mode 100644 index 0000000000000000000000000000000000000000..ea3b23fe8d4b3a45be89f9286e617624007290be Binary files /dev/null and b/src/InteractiveWindow/VisualStudio/Resources/Package.ico differ diff --git a/src/InteractiveWindow/VisualStudio/Utils.cs b/src/InteractiveWindow/VisualStudio/Utils.cs new file mode 100644 index 0000000000000000000000000000000000000000..9822203a3ba5ca3103df5b54eaf5ee42cc2cfc2e --- /dev/null +++ b/src/InteractiveWindow/VisualStudio/Utils.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; + +namespace Roslyn.VisualStudio.InteractiveWindow +{ + internal static class SpecializedCollections + { + internal static class Empty + { + internal class Array + { + public static readonly T[] Instance = new T[0]; + } + } + + public static T[] EmptyArray() + { + return Empty.Array.Instance; + } + } + + internal static class ExceptionUtilities + { + [DebuggerDisplay("Unreachable")] + public static Exception Unreachable + { + get + { + Debug.Fail("This code path should not be reachable"); + return new InvalidOperationException(); + } + } + } +} diff --git a/src/InteractiveWindow/VisualStudio/VSInteractiveWindowResources.Designer.cs b/src/InteractiveWindow/VisualStudio/VSInteractiveWindowResources.Designer.cs new file mode 100644 index 0000000000000000000000000000000000000000..d6942f874e62f9c7f5a108bd72503c4d36ad4267 --- /dev/null +++ b/src/InteractiveWindow/VisualStudio/VSInteractiveWindowResources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.18408 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.VisualStudio { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class VSInteractiveWindowResources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal VSInteractiveWindowResources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.VisualStudio.VSInteractiveWindowResources", typeof(VSInteractiveWindowResources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/src/InteractiveWindow/VisualStudio/VSInteractiveWindowResources.resx b/src/InteractiveWindow/VisualStudio/VSInteractiveWindowResources.resx new file mode 100644 index 0000000000000000000000000000000000000000..4fdb1b6aff69ba96d81420fab7a92b738c17f074 --- /dev/null +++ b/src/InteractiveWindow/VisualStudio/VSInteractiveWindowResources.resx @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/InteractiveWindow/VisualStudio/VSPackage.resx b/src/InteractiveWindow/VisualStudio/VSPackage.resx new file mode 100644 index 0000000000000000000000000000000000000000..4aaa53dfa23f8cb74d1d2e435b07132ed6d42159 --- /dev/null +++ b/src/InteractiveWindow/VisualStudio/VSPackage.resx @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Microsoft Visual Studio Interactive Window + + + Microsoft Visual Studio Interactive Window + + + Interactive Window + + + + Resources\Package.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/src/InteractiveWindow/VisualStudio/VisualStudioInteractiveWindow.csproj b/src/InteractiveWindow/VisualStudio/VisualStudioInteractiveWindow.csproj new file mode 100644 index 0000000000000000000000000000000000000000..acf9c7e12ce00238ebe7ccbfb7348a6ccf231c27 --- /dev/null +++ b/src/InteractiveWindow/VisualStudio/VisualStudioInteractiveWindow.csproj @@ -0,0 +1,125 @@ + + + + CSharp + true + + + + + + + Debug + AnyCPU + {20BB6FAC-44D2-4D76-ABFE-0C1E163A1A4F} + Library + true + + false + false + Microsoft.VisualStudio + Roslyn.VisualStudio.InteractiveWindow + Program + $(DevEnvDir)devenv.exe + /rootsuffix RoslynDev /log + AnyCPU + $(VisualStudioVersion) + true + ..\..\..\ + + + + {01E9BD68-0339-4A13-B42F-A3CA84D164F3} + InteractiveWindow + + + + MinimumRecommendedRules.ruleset + + + MinimumRecommendedRules.ruleset + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + VSInteractiveWindowResources.resx + + + + + ResXFileCodeGenerator + VSInteractiveWindowResources.Designer.cs + + + true + VSPackage + Designer + + + + + Menus.ctmenu + Designer + + + + + + + + + + + + + diff --git a/src/InteractiveWindow/VisualStudio/VsInteractiveWindow.cs b/src/InteractiveWindow/VisualStudio/VsInteractiveWindow.cs new file mode 100644 index 0000000000000000000000000000000000000000..c9f1f60333b4698e0b27d069d9b13f09f8589ee9 --- /dev/null +++ b/src/InteractiveWindow/VisualStudio/VsInteractiveWindow.cs @@ -0,0 +1,306 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +// Dumps commands in QueryStatus and Exec. +// #define DUMP_COMMANDS + +using System; +using System.Windows; +using System.Windows.Input; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.ComponentModelHost; +using Microsoft.VisualStudio.Editor; +using Microsoft.VisualStudio.OLE.Interop; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.TextManager.Interop; +using Roslyn.Editor.InteractiveWindow; + +namespace Roslyn.VisualStudio.InteractiveWindow +{ + /// + /// Default tool window for hosting interactive windows inside of Visual Studio. This hooks up support for + /// find in windows, forwarding commands down to the text view adapter, and providing access for setting + /// VS specific concepts (such as language service GUIDs) for the interactive window. + /// + /// Interactive windows can also be hosted outside of this tool window if the user creates an IInteractiveWindow + /// directly. In that case the user is responsible for doing what this class does themselves. But the + /// interactive window will be properly initialized for running inside of Visual Studio's process by our + /// VsInteractiveWindowEditorsFactoryService which handles all of the mapping of VS commands to API calls + /// on the interactive window. + /// + internal sealed class VsInteractiveWindow : ToolWindowPane, IVsFindTarget, IOleCommandTarget, IVsInteractiveWindow + { + private readonly IComponentModel _componentModel; + private readonly IVsEditorAdaptersFactoryService _editorAdapters; + + private IInteractiveWindow _window; + private IVsFindTarget _findTarget; + private IOleCommandTarget _commandTarget; + private IInteractiveEvaluator _evaluator; + private IWpfTextViewHost _textViewHost; + private readonly IVsInteractiveWindowEditorsFactoryService _editorFactoryService; + + internal VsInteractiveWindow(IComponentModel model, Guid providerId, int instanceId, string title, IInteractiveEvaluator evaluator) + { + _componentModel = model; + this.Caption = title; + _editorAdapters = _componentModel.GetService(); + _evaluator = evaluator; + _editorFactoryService = model.GetService(); + + // The following calls this.OnCreate: + Guid clsId = this.ToolClsid; + Guid empty = Guid.Empty; + Guid typeId = providerId; + IVsWindowFrame frame; + var vsShell = (IVsUIShell)ServiceProvider.GlobalProvider.GetService(typeof(SVsUIShell)); + + // we don't pass __VSCREATETOOLWIN.CTW_fMultiInstance because multi instance panes are + // destroyed when closed. We are really multi instance but we don't want to be closed. + + ErrorHandler.ThrowOnFailure( + vsShell.CreateToolWindow( + (uint)(__VSCREATETOOLWIN.CTW_fInitNew | __VSCREATETOOLWIN.CTW_fForceCreate | __VSCREATETOOLWIN.CTW_fToolbarHost), + (uint)instanceId, + this.GetIVsWindowPane(), + ref clsId, + ref typeId, + ref empty, + null, + title, + null, + out frame + ) + ); + + this.Frame = frame; + } + + public void SetLanguage(Guid languageServiceGuid) + { + _editorFactoryService.SetLanguage(_window, languageServiceGuid); + } + + public IInteractiveWindow InteractiveWindow { get { return _window; } } + + #region ToolWindowPane overrides + + protected override void OnCreate() + { + _window = _componentModel.GetService().CreateWindow(_evaluator); + _window.SubmissionBufferAdded += SubmissionBufferAdded; + _textViewHost = _editorFactoryService.GetTextViewHost(_window); + var viewAdapter = _editorAdapters.GetViewAdapter(_textViewHost.TextView); + _findTarget = viewAdapter as IVsFindTarget; + _commandTarget = viewAdapter as IOleCommandTarget; + } + + private void SubmissionBufferAdded(object sender, SubmissionBufferAddedEventArgs e) + { + GetToolbarHost().ForceUpdateUI(); + } + + protected override void OnClose() + { + _window.Close(); + base.OnClose(); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (_window != null) + { + _window.Dispose(); + } + } + } + + /// + /// This property returns the control that should be hosted in the Tool Window. It can be + /// either a FrameworkElement (for easy creation of tool windows hosting WPF content), or it + /// can be an object implementing one of the IVsUIWPFElement or IVsUIWin32Element + /// interfaces. + /// + public override object Content + { + get { return _textViewHost; } + set { } + } + + public override void OnToolWindowCreated() + { + Guid commandUiGuid = VSConstants.GUID_TextEditorFactory; + ((IVsWindowFrame)Frame).SetGuidProperty((int)__VSFPROPID.VSFPROPID_InheritKeyBindings, ref commandUiGuid); + + base.OnToolWindowCreated(); + + // add our toolbar which is defined in our VSCT file + var toolbarHost = GetToolbarHost(); + Guid guidInteractiveCmdSet = Guids.InteractiveCommandSetId; + ErrorHandler.ThrowOnFailure(toolbarHost.AddToolbar(VSTWT_LOCATION.VSTWT_TOP, ref guidInteractiveCmdSet, (uint)MenuIds.InteractiveWindowToolbar)); + } + + #endregion + + #region Window IOleCommandTarget + + public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText) + { + return _commandTarget.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText); + } + + public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) + { + return _commandTarget.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut); + } + + #endregion + + #region IVsInteractiveWindow + + public void Show(bool focus) + { + var windowFrame = (IVsWindowFrame)Frame; + ErrorHandler.ThrowOnFailure(focus ? windowFrame.Show() : windowFrame.ShowNoActivate()); + + if (focus) + { + IInputElement input = _window.TextView as IInputElement; + if (input != null) + { + Keyboard.Focus(input); + } + } + } + + private IVsToolWindowToolbarHost GetToolbarHost() + { + var frame = (IVsWindowFrame)Frame; + object result; + ErrorHandler.ThrowOnFailure(frame.GetProperty((int)__VSFPROPID.VSFPROPID_ToolbarHost, out result)); + return (IVsToolWindowToolbarHost)result; + } + + #endregion + + #region IVsFindTarget + + public int Find(string pszSearch, uint grfOptions, int fResetStartPoint, IVsFindHelper pHelper, out uint pResult) + { + if (_findTarget != null) + { + return _findTarget.Find(pszSearch, grfOptions, fResetStartPoint, pHelper, out pResult); + } + pResult = 0; + return VSConstants.E_NOTIMPL; + } + + public int GetCapabilities(bool[] pfImage, uint[] pgrfOptions) + { + if (_findTarget != null && pgrfOptions != null && pgrfOptions.Length > 0) + { + return _findTarget.GetCapabilities(pfImage, pgrfOptions); + } + return VSConstants.E_NOTIMPL; + } + + public int GetCurrentSpan(TextSpan[] pts) + { + if (_findTarget != null) + { + return _findTarget.GetCurrentSpan(pts); + } + return VSConstants.E_NOTIMPL; + } + + public int GetFindState(out object ppunk) + { + if (_findTarget != null) + { + return _findTarget.GetFindState(out ppunk); + } + ppunk = null; + return VSConstants.E_NOTIMPL; + } + + public int GetMatchRect(RECT[] prc) + { + if (_findTarget != null) + { + return _findTarget.GetMatchRect(prc); + } + return VSConstants.E_NOTIMPL; + } + + public int GetProperty(uint propid, out object pvar) + { + if (_findTarget != null) + { + return _findTarget.GetProperty(propid, out pvar); + } + pvar = null; + return VSConstants.E_NOTIMPL; + } + + public int GetSearchImage(uint grfOptions, IVsTextSpanSet[] ppSpans, out IVsTextImage ppTextImage) + { + if (_findTarget != null) + { + return _findTarget.GetSearchImage(grfOptions, ppSpans, out ppTextImage); + } + ppTextImage = null; + return VSConstants.E_NOTIMPL; + } + + public int MarkSpan(TextSpan[] pts) + { + if (_findTarget != null) + { + return _findTarget.MarkSpan(pts); + } + return VSConstants.E_NOTIMPL; + } + + public int NavigateTo(TextSpan[] pts) + { + if (_findTarget != null) + { + return _findTarget.NavigateTo(pts); + } + return VSConstants.E_NOTIMPL; + } + + public int NotifyFindTarget(uint notification) + { + if (_findTarget != null) + { + return _findTarget.NotifyFindTarget(notification); + } + return VSConstants.E_NOTIMPL; + } + + public int Replace(string pszSearch, string pszReplace, uint grfOptions, int fResetStartPoint, IVsFindHelper pHelper, out int pfReplaced) + { + if (_findTarget != null) + { + return _findTarget.Replace(pszSearch, pszReplace, grfOptions, fResetStartPoint, pHelper, out pfReplaced); + } + pfReplaced = 0; + return VSConstants.E_NOTIMPL; + } + + public int SetFindState(object pUnk) + { + if (_findTarget != null) + { + return _findTarget.SetFindState(pUnk); + } + return VSConstants.E_NOTIMPL; + } + + #endregion + } +} diff --git a/src/InteractiveWindow/VisualStudio/VsInteractiveWindowCommandFilter.cs b/src/InteractiveWindow/VisualStudio/VsInteractiveWindowCommandFilter.cs new file mode 100644 index 0000000000000000000000000000000000000000..26c008eb0f5f8f712c79bdc2ad951674ee13015c --- /dev/null +++ b/src/InteractiveWindow/VisualStudio/VsInteractiveWindowCommandFilter.cs @@ -0,0 +1,415 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +// Dumps commands in QueryStatus and Exec. +// #define DUMP_COMMANDS + +using System; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Editor; +using Microsoft.VisualStudio.OLE.Interop; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.TextManager.Interop; +using Roslyn.Editor.InteractiveWindow; + +namespace Roslyn.VisualStudio.InteractiveWindow +{ + internal sealed class VsInteractiveWindowCommandFilter : IOleCommandTarget + { + // + // Command filter chain: + // *window* -> VsTextView -> ... -> *pre-language + the current language service's filter* -> editor services -> *preEditor* -> editor + // + private IOleCommandTarget _preLanguageCommandFilter; + private IOleCommandTarget _editorServicesCommandFilter; + private IOleCommandTarget _preEditorCommandFilter; + private IOleCommandTarget _editorCommandFilter; + // we route undo/redo commands to this target: + internal IOleCommandTarget currentBufferCommandHandler; + internal IOleCommandTarget firstLanguageServiceCommandFilter; + private readonly IInteractiveWindow _window; + internal readonly IVsTextView textViewAdapter; + private readonly IWpfTextViewHost _textViewHost; + + public VsInteractiveWindowCommandFilter(IVsEditorAdaptersFactoryService adapterFactory, IInteractiveWindow window, IVsTextView textViewAdapter, IVsTextBuffer bufferAdapter) + { + _window = window; + this.textViewAdapter = textViewAdapter; + + // make us a code window so we'll have the same colors as a normal code window. + IVsTextEditorPropertyContainer propContainer; + ErrorHandler.ThrowOnFailure(((IVsTextEditorPropertyCategoryContainer)textViewAdapter).GetPropertyCategory(Microsoft.VisualStudio.Editor.DefGuidList.guidEditPropCategoryViewMasterSettings, out propContainer)); + propContainer.SetProperty(VSEDITPROPID.VSEDITPROPID_ViewComposite_AllCodeWindowDefaults, true); + propContainer.SetProperty(VSEDITPROPID.VSEDITPROPID_ViewGlobalOpt_AutoScrollCaretOnTextEntry, true); + + // editor services are initialized in textViewAdapter.Initialize - hook underneath them: + _preEditorCommandFilter = new CommandFilter(this, CommandFilterLayer.PreEditor); + ErrorHandler.ThrowOnFailure(textViewAdapter.AddCommandFilter(_preEditorCommandFilter, out _editorCommandFilter)); + + textViewAdapter.Initialize( + (IVsTextLines)bufferAdapter, + IntPtr.Zero, + (uint)TextViewInitFlags.VIF_HSCROLL | (uint)TextViewInitFlags.VIF_VSCROLL | (uint)TextViewInitFlags3.VIF_NO_HWND_SUPPORT, + new[] { new INITVIEW { fSelectionMargin = 0, fWidgetMargin = 0, fVirtualSpace = 0, fDragDropMove = 1 } }); + + // disable change tracking because everything will be changed + var textViewHost = adapterFactory.GetWpfTextViewHost(textViewAdapter); + + _preLanguageCommandFilter = new CommandFilter(this, CommandFilterLayer.PreLanguage); + ErrorHandler.ThrowOnFailure(textViewAdapter.AddCommandFilter(_preLanguageCommandFilter, out _editorServicesCommandFilter)); + + _textViewHost = textViewHost; + } + + private IOleCommandTarget TextViewCommandFilterChain + { + get + { + // Non-character command processing starts with WindowFrame which calls ReplWindow.Exec. + // We need to invoke the view's Exec method in order to invoke its full command chain + // (features add their filters to the view). + return (IOleCommandTarget)textViewAdapter; + } + } + + #region IOleCommandTarget + + public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText) + { + var nextTarget = TextViewCommandFilterChain; + + return nextTarget.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText); + } + + public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) + { + var nextTarget = TextViewCommandFilterChain; + + return nextTarget.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut); + } + + #endregion + + public IWpfTextViewHost TextViewHost + { + get + { + return _textViewHost; + } + } + + private enum CommandFilterLayer + { + PreLanguage, + PreEditor + } + + private sealed class CommandFilter : IOleCommandTarget + { + private readonly VsInteractiveWindowCommandFilter _window; + private readonly CommandFilterLayer _layer; + + public CommandFilter(VsInteractiveWindowCommandFilter window, CommandFilterLayer layer) + { + _window = window; + _layer = layer; + } + + public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText) + { + switch (_layer) + { + case CommandFilterLayer.PreLanguage: + return _window.PreLanguageCommandFilterQueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText); + + case CommandFilterLayer.PreEditor: + return _window.PreEditorCommandFilterQueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText); + } + + throw new InvalidOperationException(); + } + + public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) + { + switch (_layer) + { + case CommandFilterLayer.PreLanguage: + return _window.PreLanguageCommandFilterExec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut); + + case CommandFilterLayer.PreEditor: + return _window.PreEditorCommandFilterExec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut); + } + + throw new InvalidOperationException(); + } + } + + private int PreEditorCommandFilterQueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText) + { + return _editorCommandFilter.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText); + } + + private int PreEditorCommandFilterExec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) + { + var nextTarget = _editorCommandFilter; + + if (pguidCmdGroup == Guids.InteractiveCommandSetId) + { + switch ((CommandIds)nCmdID) + { + case CommandIds.BreakLine: + if (_window.BreakLine()) + { + return VSConstants.S_OK; + } + break; + } + } + else if (pguidCmdGroup == VSConstants.VSStd2K) + { + switch ((VSConstants.VSStd2KCmdID)nCmdID) + { + case VSConstants.VSStd2KCmdID.RETURN: + if (_window.Return()) + { + return VSConstants.S_OK; + } + break; + + // TODO: + //case VSConstants.VSStd2KCmdID.DELETEWORDLEFT: + //case VSConstants.VSStd2KCmdID.DELETEWORDRIGHT: + // break; + + case VSConstants.VSStd2KCmdID.BACKSPACE: + if (_window.Backspace()) + { + return VSConstants.S_OK; + } + break; + + + case VSConstants.VSStd2KCmdID.UP: + + if (_window.CurrentLanguageBuffer != null && !_window.IsRunning && CaretAtEnd && UseSmartUpDown) + { + _window.HistoryPrevious(); + return VSConstants.S_OK; + } + break; + + case VSConstants.VSStd2KCmdID.DOWN: + if (_window.CurrentLanguageBuffer != null && !_window.IsRunning && CaretAtEnd && UseSmartUpDown) + { + _window.HistoryNext(); + return VSConstants.S_OK; + } + break; + + case VSConstants.VSStd2KCmdID.CANCEL: + if (_window.TextView.Selection.IsEmpty) + { + _window.Cancel(); + } + break; + + case VSConstants.VSStd2KCmdID.BOL: + _window.Home(false); + return VSConstants.S_OK; + + case VSConstants.VSStd2KCmdID.BOL_EXT: + _window.Home(true); + return VSConstants.S_OK; + + case VSConstants.VSStd2KCmdID.EOL: + _window.End(false); + return VSConstants.S_OK; + + case VSConstants.VSStd2KCmdID.EOL_EXT: + _window.End(true); + return VSConstants.S_OK; + } + } + else if (pguidCmdGroup == VSConstants.GUID_VSStandardCommandSet97) + { + // undo/redo support: + switch ((VSConstants.VSStd97CmdID)nCmdID) + { + case VSConstants.VSStd97CmdID.Paste: + _window.Paste(); + return VSConstants.S_OK; + + case VSConstants.VSStd97CmdID.Cut: + _window.Cut(); + return VSConstants.S_OK; + + case VSConstants.VSStd97CmdID.Delete: + if (_window.Delete()) + { + return VSConstants.S_OK; + } + break; + + case VSConstants.VSStd97CmdID.SelectAll: + _window.SelectAll(); + return VSConstants.S_OK; + } + } + + return nextTarget.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut); + } + + private int PreLanguageCommandFilterQueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText) + { + var nextTarget = firstLanguageServiceCommandFilter; + + if (pguidCmdGroup == Guids.InteractiveCommandSetId) + { + switch ((CommandIds)prgCmds[0].cmdID) + { + case CommandIds.HistoryNext: + case CommandIds.HistoryPrevious: + case CommandIds.SearchHistoryNext: + case CommandIds.SearchHistoryPrevious: + // TODO: Submit? + prgCmds[0].cmdf = _window.CurrentLanguageBuffer != null ? CommandEnabled : CommandDisabled; + break; + case CommandIds.AbortExecution: + prgCmds[0].cmdf = _window.IsRunning ? CommandEnabled : CommandDisabled; + break; + case CommandIds.Reset: + prgCmds[0].cmdf = !_window.IsResetting ? CommandEnabled : CommandDisabled; + break; + default: + prgCmds[0].cmdf = CommandEnabled; + break; + } + prgCmds[0].cmdf |= (uint)OLECMDF.OLECMDF_DEFHIDEONCTXTMENU; + } + else if (currentBufferCommandHandler != null && pguidCmdGroup == VSConstants.GUID_VSStandardCommandSet97) + { + // undo/redo support: + switch ((VSConstants.VSStd97CmdID)prgCmds[0].cmdID) + { + case VSConstants.VSStd97CmdID.Undo: + case VSConstants.VSStd97CmdID.MultiLevelUndo: + case VSConstants.VSStd97CmdID.MultiLevelUndoList: + case VSConstants.VSStd97CmdID.Redo: + case VSConstants.VSStd97CmdID.MultiLevelRedo: + case VSConstants.VSStd97CmdID.MultiLevelRedoList: + return currentBufferCommandHandler.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText); + } + } + + var result = nextTarget.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText); +#if DUMP_COMMANDS + //DumpCmd("QS", result, ref pguidCmdGroup, prgCmds[0].cmdID, prgCmds[0].cmdf); +#endif + return result; + } + + private int PreLanguageCommandFilterExec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) + { + var nextTarget = firstLanguageServiceCommandFilter ?? _editorServicesCommandFilter; + + if (pguidCmdGroup == Guids.InteractiveCommandSetId) + { + switch ((CommandIds)nCmdID) + { + case CommandIds.AbortExecution: _window.AbortCommand(); return VSConstants.S_OK; + case CommandIds.Reset: _window.ResetAsync(); return VSConstants.S_OK; + case CommandIds.SmartExecute: _window.ExecuteInput(); return VSConstants.S_OK; + case CommandIds.HistoryNext: _window.HistoryNext(); return VSConstants.S_OK; + case CommandIds.HistoryPrevious: _window.HistoryPrevious(); return VSConstants.S_OK; + case CommandIds.ClearScreen: _window.ClearView(); return VSConstants.S_OK; + case CommandIds.SearchHistoryNext: + _window.HistorySearchNext(); + return VSConstants.S_OK; + case CommandIds.SearchHistoryPrevious: + _window.HistorySearchPrevious(); + return VSConstants.S_OK; + } + } + else if (pguidCmdGroup == VSConstants.VSStd2K) + { + switch ((VSConstants.VSStd2KCmdID)nCmdID) + { + case VSConstants.VSStd2KCmdID.TYPECHAR: + _window.Delete(); + break; + + case VSConstants.VSStd2KCmdID.RETURN: + if (_window.TrySubmitStandardInput()) + { + return VSConstants.S_OK; + } + break; + case VSConstants.VSStd2KCmdID.SHOWCONTEXTMENU: + ShowContextMenu(); + return VSConstants.S_OK; + } + } + else if (currentBufferCommandHandler != null && pguidCmdGroup == VSConstants.GUID_VSStandardCommandSet97) + { + // undo/redo support: + switch ((VSConstants.VSStd97CmdID)nCmdID) + { + case VSConstants.VSStd97CmdID.Undo: + case VSConstants.VSStd97CmdID.MultiLevelUndo: + case VSConstants.VSStd97CmdID.MultiLevelUndoList: + case VSConstants.VSStd97CmdID.Redo: + case VSConstants.VSStd97CmdID.MultiLevelRedo: + case VSConstants.VSStd97CmdID.MultiLevelRedoList: + return currentBufferCommandHandler.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut); + } + } + + int res = nextTarget.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut); +#if DUMP_COMMANDS + DumpCmd("Exec", result, ref pguidCmdGroup, nCmdID, 0); +#endif + return res; + } + + private void ShowContextMenu() + { + var uishell = (IVsUIShell)InteractiveWindowPackage.GetGlobalService(typeof(SVsUIShell)); + if (uishell != null) + { + var pt = System.Windows.Forms.Cursor.Position; + var position = new[] { new POINTS { x = (short)pt.X, y = (short)pt.Y } }; + var guid = Guids.InteractiveCommandSetId; + ErrorHandler.ThrowOnFailure(uishell.ShowContextMenu(0, ref guid, (int)MenuIds.InteractiveWindowContextMenu, position, this)); + } + } + + private const uint CommandEnabled = (uint)(OLECMDF.OLECMDF_ENABLED | OLECMDF.OLECMDF_SUPPORTED); + private const uint CommandDisabled = (uint)(OLECMDF.OLECMDF_SUPPORTED); + private const uint CommandDisabledAndHidden = (uint)(OLECMDF.OLECMDF_INVISIBLE | OLECMDF.OLECMDF_SUPPORTED); + + private bool CaretAtEnd + { + get + { + var caret = _window.TextView.Caret; + return caret.Position.BufferPosition.Position == caret.Position.BufferPosition.Snapshot.Length; + } + } + + private bool UseSmartUpDown + { + get + { + return _window.TextView.Options.GetOptionValue(InteractiveWindowOptions.SmartUpDown); + } + } + + public IOleCommandTarget EditorServicesCommandFilter + { + get + { + return _editorServicesCommandFilter; + } + } + } +} diff --git a/src/InteractiveWindow/VisualStudio/VsInteractiveWindowEditorFactoryService.cs b/src/InteractiveWindow/VisualStudio/VsInteractiveWindowEditorFactoryService.cs new file mode 100644 index 0000000000000000000000000000000000000000..d4b28002b9501d6d91df54e63bf78db8afd99e5c --- /dev/null +++ b/src/InteractiveWindow/VisualStudio/VsInteractiveWindowEditorFactoryService.cs @@ -0,0 +1,125 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Windows; +using System.Windows.Threading; +using Microsoft.VisualStudio.Editor; +using Microsoft.VisualStudio.OLE.Interop; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.TextManager.Interop; +using Microsoft.VisualStudio.Utilities; +using Roslyn.Editor.InteractiveWindow; +using Roslyn.VisualStudio.InteractiveWindow; + +namespace Microsoft.VisualStudio +{ + using IOleServiceProvider = Microsoft.VisualStudio.OLE.Interop.IServiceProvider; + + [Export(typeof(IInteractiveWindowEditorFactoryService))] + [Export(typeof(IVsInteractiveWindowEditorsFactoryService))] + internal sealed class VsInteractiveWindowEditorFactoryService : IInteractiveWindowEditorFactoryService, IVsInteractiveWindowEditorsFactoryService + { + private readonly IOleServiceProvider _provider; + private readonly IVsEditorAdaptersFactoryService _adapterFactory; + private readonly IContentTypeRegistryService _contentTypeRegistry; + private readonly IEnumerable> _oleCommandTargetProviders; + + [ImportingConstructor] + public VsInteractiveWindowEditorFactoryService(IVsEditorAdaptersFactoryService adaptersFactory, IContentTypeRegistryService contentTypeRegistry, [ImportMany]IEnumerable> oleCommandTargetProviders) + { + _adapterFactory = adaptersFactory; + _provider = (IOleServiceProvider)InteractiveWindowPackage.GetGlobalService(typeof(IOleServiceProvider)); + _contentTypeRegistry = contentTypeRegistry; + _oleCommandTargetProviders = oleCommandTargetProviders; + } + + IWpfTextView IInteractiveWindowEditorFactoryService.CreateTextView(IInteractiveWindow window, ITextBuffer buffer, ITextViewRoleSet roles) + { + var bufferAdapter = _adapterFactory.CreateVsTextBufferAdapterForSecondaryBuffer(_provider, buffer); + + // Create and initialize text view adapter. + // WARNING: This might trigger various services like IntelliSense, margins, taggers, etc. + var textViewAdapter = _adapterFactory.CreateVsTextViewAdapter(_provider, roles); + + var commandFilter = new VsInteractiveWindowCommandFilter(_adapterFactory, window, textViewAdapter, bufferAdapter); + window.Properties[typeof(VsInteractiveWindowCommandFilter)] = commandFilter; + return commandFilter.TextViewHost.TextView; + } + + ITextBuffer IInteractiveWindowEditorFactoryService.CreateAndActivateBuffer(IInteractiveWindow window, IContentType contentType) + { + // create buffer adapter to support undo/redo: + var bufferAdapter = _adapterFactory.CreateVsTextBufferAdapter(_provider, contentType); + bufferAdapter.InitializeContent("", 0); + + var commandFilter = GetCommandFilter(window); + if (commandFilter.currentBufferCommandHandler != null) + { + ((IVsPersistDocData)commandFilter.currentBufferCommandHandler).Close(); + } + + commandFilter.currentBufferCommandHandler = (IOleCommandTarget)bufferAdapter; + + return _adapterFactory.GetDocumentBuffer(bufferAdapter); + } + + public IWpfTextViewHost GetTextViewHost(IInteractiveWindow window) + { + var cmdFilter = GetCommandFilter(window); + if (cmdFilter != null) + { + return cmdFilter.TextViewHost; + } + return null; + } + + public void SetLanguage(IInteractiveWindow window, Guid languageServiceGuid) + { + // REVIEW: What happens to the current input buffer here? + // REVIEW: What happens when the window is already initialized? + GetDispatcher(window).CheckAccess(); + + var commandFilter = GetCommandFilter(window); + commandFilter.firstLanguageServiceCommandFilter = null; + var provider = _oleCommandTargetProviders.OfContentType(window.Evaluator.ContentType, _contentTypeRegistry); + if (provider != null) + { + var targetFilter = commandFilter.firstLanguageServiceCommandFilter ?? GetCommandFilter(window).EditorServicesCommandFilter; + var target = provider.GetCommandTarget(window.TextView, targetFilter); + if (target != null) + { + commandFilter.firstLanguageServiceCommandFilter = target; + } + } + + SetEditorOptions(window.TextView.Options, languageServiceGuid); + } + + private void SetEditorOptions(IEditorOptions options, Guid languageServiceGuid) + { + IVsTextManager textMgr = (IVsTextManager)InteractiveWindowPackage.GetGlobalService(typeof(SVsTextManager)); + var langPrefs = new LANGPREFERENCES[1]; + langPrefs[0].guidLang = languageServiceGuid; + ErrorHandler.ThrowOnFailure(textMgr.GetUserPreferences(null, null, langPrefs, null)); + + options.SetOptionValue(DefaultTextViewHostOptions.ChangeTrackingId, false); + options.SetOptionValue(DefaultOptions.ConvertTabsToSpacesOptionId, langPrefs[0].fInsertTabs == 0); + options.SetOptionValue(DefaultOptions.TabSizeOptionId, (int)langPrefs[0].uTabSize); + options.SetOptionValue(DefaultOptions.IndentSizeOptionId, (int)langPrefs[0].uIndentSize); + } + + private Dispatcher GetDispatcher(IInteractiveWindow window) + { + return ((FrameworkElement)window.TextView).Dispatcher; + } + + public VsInteractiveWindowCommandFilter GetCommandFilter(IInteractiveWindow window) + { + return (VsInteractiveWindowCommandFilter)window.Properties[typeof(VsInteractiveWindowCommandFilter)]; + } + } +} diff --git a/src/InteractiveWindow/VisualStudio/VsInteractiveWindowFactory.cs b/src/InteractiveWindow/VisualStudio/VsInteractiveWindowFactory.cs new file mode 100644 index 0000000000000000000000000000000000000000..6cfd93a610c60446188c908375d453e2539ebcdc --- /dev/null +++ b/src/InteractiveWindow/VisualStudio/VsInteractiveWindowFactory.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.ComponentModel.Composition; +using Microsoft.VisualStudio.ComponentModelHost; +using Microsoft.VisualStudio.Shell; +using Roslyn.Editor.InteractiveWindow; + +namespace Roslyn.VisualStudio.InteractiveWindow +{ + [Export(typeof(IVsInteractiveWindowFactory))] + internal sealed class VsInteractiveWindowFactory : IVsInteractiveWindowFactory + { + private readonly IComponentModel _componentModel; + + [ImportingConstructor] + internal VsInteractiveWindowFactory(SVsServiceProvider serviceProvider) + { + _componentModel = (IComponentModel)serviceProvider.GetService(typeof(SComponentModel)); + } + + public IVsInteractiveWindow Create(Guid providerId, int instanceId, string title, IInteractiveEvaluator evaluator) + { + return new VsInteractiveWindow(_componentModel, providerId, instanceId, title, evaluator); + } + } +} diff --git a/src/InteractiveWindow/VisualStudio/packages.config b/src/InteractiveWindow/VisualStudio/packages.config new file mode 100644 index 0000000000000000000000000000000000000000..79ece06bef6a23c7f75492ed821f546d8bd403e2 --- /dev/null +++ b/src/InteractiveWindow/VisualStudio/packages.config @@ -0,0 +1,3 @@ + + + diff --git a/src/Roslyn.sln b/src/Roslyn.sln index b17111ff0d4e9a51a899395bf1c7fc8ceed15072..2942336b953af3a08e5042f959b1e485e3f7a2cf 100644 --- a/src/Roslyn.sln +++ b/src/Roslyn.sln @@ -219,6 +219,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "csi", "Interactive\csi\csi. EndProject Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "vbi", "Interactive\vbi\vbi.vbproj", "{6E62A0FF-D0DC-4109-9131-AB8E60CDFF7B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InteractiveWindowTest", "InteractiveWindow\EditorTest\InteractiveWindowTest.csproj", "{7F3CB45E-4993-4FA4-8D6A-C2DFFED2DFC3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VisualStudioInteractiveWindow", "InteractiveWindow\VisualStudio\VisualStudioInteractiveWindow.csproj", "{20BB6FAC-44D2-4D76-ABFE-0C1E163A1A4F}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution Compilers\Core\SharedCollections\SharedCollections.projitems*{5f8d2414-064a-4b3a-9b42-8e2a04246be5}*SharedItemsImports = 4 @@ -1297,6 +1301,18 @@ Global {6E62A0FF-D0DC-4109-9131-AB8E60CDFF7B}.Release|Mixed Platforms.ActiveCfg = Release|x86 {6E62A0FF-D0DC-4109-9131-AB8E60CDFF7B}.Release|Mixed Platforms.Build.0 = Release|x86 {6E62A0FF-D0DC-4109-9131-AB8E60CDFF7B}.Release|x64.ActiveCfg = Release|x86 + {7F3CB45E-4993-4FA4-8D6A-C2DFFED2DFC3}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {7F3CB45E-4993-4FA4-8D6A-C2DFFED2DFC3}.Debug|Mixed Platforms.Build.0 = Debug|x86 + {7F3CB45E-4993-4FA4-8D6A-C2DFFED2DFC3}.Debug|x64.ActiveCfg = Debug|x86 + {7F3CB45E-4993-4FA4-8D6A-C2DFFED2DFC3}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {7F3CB45E-4993-4FA4-8D6A-C2DFFED2DFC3}.Release|Mixed Platforms.Build.0 = Release|x86 + {7F3CB45E-4993-4FA4-8D6A-C2DFFED2DFC3}.Release|x64.ActiveCfg = Release|x86 + {20BB6FAC-44D2-4D76-ABFE-0C1E163A1A4F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {20BB6FAC-44D2-4D76-ABFE-0C1E163A1A4F}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {20BB6FAC-44D2-4D76-ABFE-0C1E163A1A4F}.Debug|x64.ActiveCfg = Debug|Any CPU + {20BB6FAC-44D2-4D76-ABFE-0C1E163A1A4F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {20BB6FAC-44D2-4D76-ABFE-0C1E163A1A4F}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {20BB6FAC-44D2-4D76-ABFE-0C1E163A1A4F}.Release|x64.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE