This commit is contained in:
Ilya Biryukov 2018-05-30 09:20:37 -07:00
Родитель 6cb23f2b29
Коммит f7fbdb4885
30 изменённых файлов: 787 добавлений и 511 удалений

Просмотреть файл

@ -0,0 +1,56 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.VisualStudio.Terminal
{
public delegate void TerminalDataRecievedEventHandler(object sender, string data);
/// <summary>
/// Terminal renderer.
/// The terminal starts in auto-fit mode where terminal size always matches the window size when window resizes.
/// After a first call to <c>ResizeAsyc()</c>, the terminal switches to fixed size mode where the terminal size stays
/// fixed and doesn't change when the window size changes. In this mode the terminal size can only be changed
/// with <c>ResizeAsync()</c>. To show the terminal inside the window, a dotted border will be displayed
/// on the bottom and right sides of it.
/// If the terminal window gets smaller than the terminal, some parts of the terminal may get clipped from view.
/// </summary>
[ComImport, Guid("150B7535-03F9-41C0-9515-17ECB8199FFE")]
public interface ITerminalRenderer : ITerminal
{
/// <summary>
/// Gets current cols of the terminal window.
/// </summary>
int Cols { get; }
/// <summary>
/// Gets current rows of the terminal window.
/// </summary>
int Rows { get; }
/// <summary>
/// An event that is fired when the terminal window is resized and either Rows or Cols have changed.
/// </summary>
event EventHandler TerminalResized;
/// <summary>
/// An event that is fired when user input is recieved.
/// </summary>
event TerminalDataRecievedEventHandler TerminalDataRecieved;
/// <summary>
/// Changes the size of the terminal.
/// After that the terminal size may not match the terminal window size.
/// <c>Rows</c> and <c>Cols</c> always match the terminal window, and may differ from the terminal size
/// after <c>ResizeAsync</c> is called.
/// Calling<c>ResizeAsync</c> doesn't cause <c>TerminalResized</c> event.
/// </summary>
Task ResizeAsync(int cols, int rows, CancellationToken cancellationToken);
/// <summary>
/// Sends data to the terminal making it render the data.
/// </summary>
Task PtyDataAsync(string data, CancellationToken cancellationToken);
}
}

Просмотреть файл

@ -0,0 +1,25 @@
using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace Microsoft.VisualStudio.Terminal
{
[ComImport, Guid("250301DA-0BEA-4C4E-B199-C363C7C164B2")]
public interface ITerminalRendererService
{
/// <summary>
/// Create a new terminal renderer instance with the given name that fits the window.
/// </summary>
/// <param name="name">The name that will be displayed as the tool window title.</param>
/// <returns>An instance of ITerminalRenderer</returns>
Task<object> CreateTerminalRendererAsync(string name);
/// <summary>
/// Create a new terminal renderer instance with the given name and dimensions.
/// The terminal size won't change when user resizes the window.
/// </summary>
/// <param name="name">The name that will be displayed as the tool window title.</param>
/// <returns>An instance of ITerminalRenderer</returns>
Task<object> CreateTerminalRendererAsync(string name, int cols, int rows);
}
}

Просмотреть файл

@ -39,6 +39,8 @@ namespace Microsoft.VisualStudio.Terminal
this.Content = host;
}
public bool HasDocument => this.browser.Document != null;
private void Browser_PreviewKeyDown(object sender, System.Windows.Forms.PreviewKeyDownEventArgs e)
{
// Forces the terminal to capture the shortcut instead of sending it to VS
@ -77,7 +79,7 @@ namespace Microsoft.VisualStudio.Terminal
public object Invoke(string scriptName, params object[] args)
{
return this.browser.Document.InvokeScript(scriptName, args);
return this.browser.Document?.InvokeScript(scriptName, args);
}
private void Browser_DocumentCompleted(object sender, System.Windows.Forms.WebBrowserDocumentCompletedEventArgs e)

Просмотреть файл

@ -1,32 +0,0 @@
namespace Microsoft.VisualStudio.Terminal
{
using Microsoft.VisualStudio.PlatformUI;
using StreamJsonRpc;
using System.Threading.Tasks;
internal class TerminalEvent
{
private readonly TermWindowPackage package;
private readonly BetterBrowser browser;
public TerminalEvent(TermWindowPackage package, BetterBrowser browser, SolutionUtils solutionUtils)
{
this.package = package;
this.browser = browser;
}
[JsonRpcMethod("PtyData")]
public async Task PtyDataAsync(string data)
{
await this.package.JoinableTaskFactory.SwitchToMainThreadAsync();
this.browser.Invoke("triggerEvent", "ptyData", data);
}
[JsonRpcMethod("PtyExit")]
public async Task PtyExitAsync(int? code)
{
await this.package.JoinableTaskFactory.SwitchToMainThreadAsync();
this.browser.Invoke("triggerEvent", "ptyExited", code);
}
}
}

Просмотреть файл

@ -1,7 +1,6 @@
using Microsoft.VisualStudio.Shell;
using StreamJsonRpc;
using Microsoft.VisualStudio.Terminal.VsService;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Windows;
@ -10,120 +9,70 @@ namespace Microsoft.VisualStudio.Terminal
{
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
[ComVisible(true)]
public class TerminalScriptingObject : ITerminalScriptingObject
public sealed class TerminalScriptingObject : ITerminalScriptingObject
{
private readonly TerminalRenderer terminal;
private readonly TermWindowPackage package;
private readonly JsonRpc ptyService;
private readonly SolutionUtils solutionUtils;
private readonly string workingDirectory;
private readonly bool useSolutionDir;
private readonly string shellPath;
private readonly IEnumerable<string> args;
private readonly IDictionary<string, string> env;
internal TerminalScriptingObject(
TermWindowPackage package,
JsonRpc ptyService,
SolutionUtils solutionUtils,
string workingDirectory,
bool useSolutionDir,
string shellPath,
IEnumerable<string> args,
IDictionary<string, string> env)
internal TerminalScriptingObject(TerminalRenderer terminal, TermWindowPackage package)
{
this.terminal = terminal;
this.package = package;
this.ptyService = ptyService;
this.solutionUtils = solutionUtils;
this.workingDirectory = workingDirectory;
this.useSolutionDir = useSolutionDir;
this.shellPath = shellPath;
this.args = args;
this.env = env;
}
public string GetTheme()
public event EventHandler<TermInitEventArgs> TerminalInit;
public string GetTheme() => TerminalThemer.GetTheme();
public string GetFontFamily() => this.package.OptionFontFamily;
public int GetFontSize() => this.package.OptionFontSize;
public void CopyStringToClipboard(string stringToCopy) => Clipboard.SetText(stringToCopy ?? "");
public string GetClipboard() => Clipboard.GetText();
public string GetLinkRegex() => TerminalRegex.LocalLinkRegex.ToString();
public void HandleLocalLink(string uri) => TerminalRegex.HandleLocalLink(uri);
public bool ValidateLocalLink(string link) => TerminalRegex.ValidateLocalLink(link);
public void ClosePty() =>
this.terminal.OnTerminalClosed();
public void InitPty(int cols, int rows, string directory) =>
TerminalInit?.Invoke(this, new TermInitEventArgs(cols, rows, directory));
public void ResizePty(int cols, int rows) =>
this.terminal.OnTerminalResized(cols, rows);
public void TermData(string data) => this.terminal.OnTerminalDataRecieved(data);
public string GetSolutionDir() => this.terminal.SolutionDirectory;
}
[DebuggerStepThrough]
public class ResizeEventArgs : EventArgs
{
public ResizeEventArgs(int cols, int rows)
{
return TerminalThemer.GetTheme();
Cols = cols;
Rows = rows;
}
public string GetFontFamily()
public int Cols { get; }
public int Rows { get; }
}
[DebuggerStepThrough]
public class TermInitEventArgs : ResizeEventArgs
{
public TermInitEventArgs(int cols, int rows, string directory) : base(cols, rows)
{
return this.package.OptionFontFamily;
Directory = directory;
}
public int GetFontSize()
{
return this.package.OptionFontSize;
}
public string GetSolutionDir()
{
if (!this.useSolutionDir)
{
return this.workingDirectory;
}
var solutionDir = this.solutionUtils.GetSolutionDir();
if (solutionDir == null)
{
solutionDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
}
return solutionDir;
}
public void ClosePty()
{
this.ptyService.InvokeAsync("closeTerm");
}
public void CopyStringToClipboard(string stringToCopy)
{
Clipboard.SetText(stringToCopy ?? "");
}
public string GetClipboard()
{
return Clipboard.GetText();
}
public string GetLinkRegex()
{
return TerminalRegex.LocalLinkRegex.ToString();
}
public void HandleLocalLink(string uri)
{
TerminalRegex.HandleLocalLink(uri);
}
public void InitPty(int cols, int rows, string directory)
{
string configuredShellPath;
if (this.package.OptionTerminal == DefaultTerminal.Other)
{
configuredShellPath = this.package.OptionShellPath;
}
else
{
configuredShellPath = this.package.OptionTerminal.ToString();
}
this.ptyService.InvokeAsync("initTerm", this.shellPath ?? configuredShellPath, cols, rows, directory, ((object)this.args) ?? this.package.OptionStartupArgument, env).FileAndForget("WhackWhackTerminal/InitPty");
}
public void ResizePty(int cols, int rows)
{
this.ptyService.InvokeAsync("resizeTerm", cols, rows).FileAndForget("WhackWhackTerminal/ResizePty");
}
public void TermData(string data)
{
this.ptyService.InvokeAsync("termData", data).FileAndForget("WhackWhackTerminal/TermData");
}
public bool ValidateLocalLink(string link)
{
return TerminalRegex.ValidateLocalLink(link);
}
public string Directory { get; }
}
}

Просмотреть файл

@ -137,5 +137,12 @@ namespace Microsoft.VisualStudio.Terminal
get;
set;
}
[DataMember(Name = "border")]
public string Border
{
get;
set;
}
}
}

Просмотреть файл

@ -63,6 +63,7 @@ namespace Microsoft.VisualStudio.Terminal
theme.Background = ColorTranslator.ToHtml(VSColorTheme.GetThemedColor(EnvironmentColors.ToolboxBackgroundColorKey));
theme.Foreground = ColorTranslator.ToHtml(VSColorTheme.GetThemedColor(EnvironmentColors.ToolboxContentTextColorKey));
theme.Cursor = ColorTranslator.ToHtml(VSColorTheme.GetThemedColor(EnvironmentColors.ToolboxContentTextColorKey));
theme.Border = ColorTranslator.ToHtml(VSColorTheme.GetThemedColor(EnvironmentColors.ToolboxDisabledContentTextColorKey));
return JsonConvert.SerializeObject(theme);
}
}

Просмотреть файл

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\Microsoft.TypeScript.MSBuild.2.8.3\build\Microsoft.TypeScript.MSBuild.props" Condition="Exists('..\packages\Microsoft.TypeScript.MSBuild.2.8.3\build\Microsoft.TypeScript.MSBuild.props')" />
<Import Project="..\packages\Microsoft.VSSDK.BuildTools.15.5.100\build\Microsoft.VSSDK.BuildTools.props" Condition="Exists('..\packages\Microsoft.VSSDK.BuildTools.15.5.100\build\Microsoft.VSSDK.BuildTools.props')" />
<Import Project="..\packages\Microsoft.TypeScript.MSBuild.2.7.1\build\Microsoft.TypeScript.MSBuild.props" Condition="Exists('..\packages\Microsoft.TypeScript.MSBuild.2.7.1\build\Microsoft.TypeScript.MSBuild.props')" />
<PropertyGroup>
<MinimumVisualStudioVersion>15.0</MinimumVisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
@ -87,13 +87,15 @@
<Compile Include="BrowserBridge\ITerminalScriptingObject.cs" />
<Compile Include="BrowserBridge\TerminalRegex.cs" />
<Compile Include="BrowserBridge\TerminalScriptingObject.cs" />
<Compile Include="BrowserBridge\TerminalEvent.cs" />
<Compile Include="BrowserBridge\TerminalThemer.cs" />
<Compile Include="ServiceToolWindow.cs" />
<Compile Include="ServiceToolWindowControl.xaml.cs">
<DependentUpon>ServiceToolWindowControl.xaml</DependentUpon>
<Compile Include="RendererWindow.cs" />
<Compile Include="TermWindowPane.cs" />
<Compile Include="TermWindow.cs" />
<Compile Include="TermWindowControl.xaml.cs">
<DependentUpon>TermWindowControl.xaml</DependentUpon>
</Compile>
<Compile Include="VsService\EmbeddedTerminal.cs" />
<Compile Include="VsService\EmbeddedTerminalOptions.cs" />
<Compile Include="VsService\EmbeddedTerminalService.cs" />
<Compile Include="BrowserBridge\TerminalTheme.cs" />
<Compile Include="Options\OptionItemAttribute.cs" />
@ -104,13 +106,9 @@
<SubType>Component</SubType>
</Compile>
<Compile Include="SolutionUtils.cs" />
<Compile Include="TermWindow.cs" />
<Compile Include="TermWindowCommand.cs" />
<Compile Include="TermWindowControl.xaml.cs">
<DependentUpon>TermWindowControl.xaml</DependentUpon>
</Compile>
<Compile Include="TermWindowPackage.cs" />
<Compile Include="ToolWindowContext.cs" />
<Compile Include="VsService\TerminalRenderer.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="BrandWhackWhackTerminal_256x.png">
@ -153,13 +151,9 @@
</VSCTCompile>
</ItemGroup>
<ItemGroup>
<Page Include="ServiceToolWindowControl.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="TermWindowControl.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
</ItemGroup>
<ItemGroup>
@ -349,6 +343,7 @@
<ItemGroup>
<TypeScriptCompile Include="WebView\ts\external.d.ts" />
<TypeScriptCompile Include="WebView\ts\main.ts" />
<TypeScriptCompile Include="WebView\ts\TerminalFit.d.ts" />
<TypeScriptCompile Include="WebView\ts\TerminalLinkMatcher.ts" />
<TypeScriptCompile Include="WebView\ts\TermView.ts" />
<TypeScriptCompile Include="WebView\ts\VsEventManager.ts" />
@ -370,12 +365,11 @@
</PropertyGroup>
<Error Condition="!Exists('..\packages\Microsoft.VSSDK.BuildTools.15.5.100\build\Microsoft.VSSDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.VSSDK.BuildTools.15.5.100\build\Microsoft.VSSDK.BuildTools.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.VSSDK.BuildTools.15.5.100\build\Microsoft.VSSDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.VSSDK.BuildTools.15.5.100\build\Microsoft.VSSDK.BuildTools.targets'))" />
<Error Condition="!Exists('..\packages\Microsoft.TypeScript.MSBuild.2.7.1\build\Microsoft.TypeScript.MSBuild.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.TypeScript.MSBuild.2.7.1\build\Microsoft.TypeScript.MSBuild.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.TypeScript.MSBuild.2.7.1\build\Microsoft.TypeScript.MSBuild.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.TypeScript.MSBuild.2.7.1\build\Microsoft.TypeScript.MSBuild.targets'))" />
<Error Condition="!Exists('..\packages\Microsoft.VisualStudio.SDK.EmbedInteropTypes.15.0.16\build\Microsoft.VisualStudio.SDK.EmbedInteropTypes.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.VisualStudio.SDK.EmbedInteropTypes.15.0.16\build\Microsoft.VisualStudio.SDK.EmbedInteropTypes.targets'))" />
<Error Condition="!Exists('..\packages\Nerdbank.GitVersioning.2.1.23\build\Nerdbank.GitVersioning.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Nerdbank.GitVersioning.2.1.23\build\Nerdbank.GitVersioning.targets'))" />
<Error Condition="!Exists('..\packages\Microsoft.TypeScript.MSBuild.2.8.3\build\Microsoft.TypeScript.MSBuild.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.TypeScript.MSBuild.2.8.3\build\Microsoft.TypeScript.MSBuild.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.TypeScript.MSBuild.2.8.3\build\Microsoft.TypeScript.MSBuild.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.TypeScript.MSBuild.2.8.3\build\Microsoft.TypeScript.MSBuild.targets'))" />
</Target>
<Import Project="..\packages\Microsoft.TypeScript.MSBuild.2.7.1\build\Microsoft.TypeScript.MSBuild.targets" Condition="Exists('..\packages\Microsoft.TypeScript.MSBuild.2.7.1\build\Microsoft.TypeScript.MSBuild.targets')" />
<Import Project="..\packages\Microsoft.VSSDK.BuildTools.15.5.100\build\Microsoft.VSSDK.BuildTools.targets" Condition="Exists('..\packages\Microsoft.VSSDK.BuildTools.15.5.100\build\Microsoft.VSSDK.BuildTools.targets')" />
<Target Name="AddTsToContent" AfterTargets="CompileTypeScript" Condition="'$(BuildingProject)' != 'false'">
<ItemGroup>
@ -405,6 +399,7 @@
</Target>
<Import Project="..\packages\Microsoft.VisualStudio.SDK.EmbedInteropTypes.15.0.16\build\Microsoft.VisualStudio.SDK.EmbedInteropTypes.targets" Condition="Exists('..\packages\Microsoft.VisualStudio.SDK.EmbedInteropTypes.15.0.16\build\Microsoft.VisualStudio.SDK.EmbedInteropTypes.targets')" />
<Import Project="..\packages\Nerdbank.GitVersioning.2.1.23\build\Nerdbank.GitVersioning.targets" Condition="Exists('..\packages\Nerdbank.GitVersioning.2.1.23\build\Nerdbank.GitVersioning.targets')" />
<Import Project="..\packages\Microsoft.TypeScript.MSBuild.2.8.3\build\Microsoft.TypeScript.MSBuild.targets" Condition="Exists('..\packages\Microsoft.TypeScript.MSBuild.2.8.3\build\Microsoft.TypeScript.MSBuild.targets')" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">

Просмотреть файл

@ -0,0 +1,21 @@
using System;
using System.Runtime.InteropServices;
namespace Microsoft.VisualStudio.Terminal
{
/// <summary>
/// This class implements the tool window exposed by this package and hosts a user control.
/// </summary>
[Guid(ToolWindowGuid)]
public class RendererWindow : TermWindowPane
{
public const string ToolWindowGuid = "D3F9A61A-63E2-4AA8-8BCF-229D733CA27E";
/// <summary>
/// Initializes a new instance of the <see cref="TermWindow"/> class.
/// </summary>
public RendererWindow(TermWindowPackage package) : base(package)
{
}
}
}

Просмотреть файл

@ -20,6 +20,7 @@ function ServicePty(stream, _host) {
this.connection.onRequest('termData', (data) => this.termData(data));
this.connection.onRequest('resizeTerm', (cols, rows) => this.resizeTerm(cols, rows));
this.connection.onRequest('closeTerm', () => this.closeTerm());
this.connection.onClose(() => this.closeTerm());
this.connection.listen();
}

Просмотреть файл

@ -1,34 +0,0 @@
namespace Microsoft.VisualStudio.Terminal
{
using System;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.Shell;
/// <summary>
/// This class implements the tool window exposed by this package and hosts a user control.
/// </summary>
/// <remarks>
/// In Visual Studio tool windows are composed of a frame (implemented by the shell) and a pane,
/// usually implemented by the package implementer.
/// <para>
/// This class derives from the ToolWindowPane class provided from the MPF in order to use its
/// implementation of the IVsUIElementPane interface.
/// </para>
/// </remarks>
[Guid(ServiceToolWindowGuid)]
public class ServiceToolWindow : ToolWindowPane
{
public const string ServiceToolWindowGuid = "ebfb63ec-6efd-4fb3-834f-e9da3a40f2a1";
/// <summary>
/// Initializes a new instance of the <see cref="ServiceToolWindow"/> class.
/// </summary>
public ServiceToolWindow(ToolWindowContext context) : base(null)
{
// This is the user control hosted by the tool window; Note that, even if this class implements IDisposable,
// we are not calling Dispose on this object. This is because ToolWindowPane calls Dispose on
// the object returned by the Content property.
this.Content = new ServiceToolWindowControl(context);
}
}
}

Просмотреть файл

@ -1,15 +0,0 @@
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Microsoft.VisualStudio.Terminal"
xmlns:platformui="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.15.0"
x:Class="Microsoft.VisualStudio.Terminal.ServiceToolWindowControl"
Background="{DynamicResource VsBrush.Window}"
Foreground="{DynamicResource VsBrush.WindowText}"
x:Name="MyToolWindow">
<Grid x:Name="mainGrid">
<local:BetterBrowser x:Name="terminalView" />
</Grid>
</UserControl>

Просмотреть файл

@ -1,78 +0,0 @@
namespace Microsoft.VisualStudio.Terminal
{
using Microsoft.VisualStudio.PlatformUI;
using StreamJsonRpc;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
/// <summary>
/// Interaction logic for ServiceToolWindowControl.
/// </summary>
public partial class ServiceToolWindowControl : UserControl, IDisposable
{
private readonly SolutionUtils solutionUtils;
private readonly TermWindowPackage package;
private readonly JsonRpc rpc;
/// <summary>
/// Initializes a new instance of the <see cref="ServiceToolWindowControl"/> class.
/// </summary>
public ServiceToolWindowControl(ToolWindowContext context)
{
this.InitializeComponent();
this.Focusable = true;
this.GotFocus += TermWindowControl_GotFocus;
var target = new TerminalEvent(context.Package, this.terminalView, context.SolutionUtils);
this.rpc = JsonRpc.Attach(context.ServiceHubStream, target);
this.package = context.Package;
this.solutionUtils = context.SolutionUtils;
}
internal void FinishInitialize(string workingDirectory, string shellPath, IEnumerable<string> args, IDictionary<string, string> env)
{
VSColorTheme.ThemeChanged += VSColorTheme_ThemeChanged;
this.terminalView.ScriptingObject = new TerminalScriptingObject(this.package, this.rpc, this.solutionUtils, workingDirectory, false, shellPath, args, env);
string extensionDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
string rootPath = Path.Combine(extensionDirectory, "WebView\\default.html").Replace("\\\\", "\\");
this.terminalView.Navigate(new Uri(rootPath));
}
public async Task ChangeWorkingDirectoryAsync(string workingDirectory)
{
await this.package.JoinableTaskFactory.SwitchToMainThreadAsync();
this.terminalView.Invoke("triggerEvent", "directoryChanged", workingDirectory);
}
public void Dispose()
{
var helper = (ITerminalScriptingObject)this.terminalView.ScriptingObject;
helper.ClosePty();
}
private void TermWindowControl_GotFocus(object sender, RoutedEventArgs e)
{
// We call focus here because if we don't, the next call will prevent the toolbar from turning blue.
// No functionality is lost when this happens but it is not consistent with VS design conventions.
this.Focus();
this.terminalView.Invoke("triggerEvent", "focus");
}
private void VSColorTheme_ThemeChanged(ThemeChangedEventArgs e)
{
this.package.JoinableTaskFactory.RunAsync(async () =>
{
await this.package.JoinableTaskFactory.SwitchToMainThreadAsync();
this.terminalView.Invoke("triggerEvent", "themeChanged", TerminalThemer.GetTheme());
});
}
}
}

Просмотреть файл

@ -1,8 +1,11 @@
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Threading;
using Microsoft.VisualStudio.Workspace.VSIntegration;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Task = System.Threading.Tasks.Task;
namespace Microsoft.VisualStudio.Terminal
@ -11,12 +14,14 @@ namespace Microsoft.VisualStudio.Terminal
{
private readonly IVsSolution solutionService;
private readonly IVsWorkspaceFactory workspaceService;
private readonly JoinableTaskFactory jtf;
public SolutionUtils(IVsSolution solutionService, IVsWorkspaceFactory workspaceService)
public SolutionUtils(IVsSolution solutionService, IVsWorkspaceFactory workspaceService, JoinableTaskFactory jtf)
{
ThreadHelper.ThrowIfNotOnUIThread();
this.solutionService = solutionService;
this.workspaceService = workspaceService;
this.jtf = jtf;
var events = new SolutionEvents(solutionService, this);
this.solutionService.AdviseSolutionEvents(events, out var cookie);
@ -25,9 +30,10 @@ namespace Microsoft.VisualStudio.Terminal
this.workspaceService.OnActiveWorkspaceChanged += WorkspaceChangedAsync;
}
public string GetSolutionDir()
public async Task<string> GetSolutionDirAsync(CancellationToken cancellationToken)
{
ThreadHelper.ThrowIfNotOnUIThread();
await this.jtf.SwitchToMainThreadAsync(cancellationToken);
this.solutionService.GetSolutionInfo(out var solutionDir, out _, out _);
// solution may sometimes be null in an open folder scenario.

Просмотреть файл

@ -1,35 +1,29 @@
namespace Microsoft.VisualStudio.Terminal
{
using System;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.Shell;
using System;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Terminal.VsService;
namespace Microsoft.VisualStudio.Terminal
{
/// <summary>
/// This class implements the tool window exposed by this package and hosts a user control.
/// </summary>
/// <remarks>
/// In Visual Studio tool windows are composed of a frame (implemented by the shell) and a pane,
/// usually implemented by the package implementer.
/// <para>
/// This class derives from the ToolWindowPane class provided from the MPF in order to use its
/// implementation of the IVsUIElementPane interface.
/// </para>
/// </remarks>
[Guid(TermWindowGuidString)]
public class TermWindow : ToolWindowPane
[Guid(ToolWindowGuid)]
public class TermWindow : TermWindowPane
{
public const string TermWindowGuidString = "f7090fd8-8163-4e9a-9616-45ff87e0816e";
public const string ToolWindowGuid = "7f989465-4c63-4820-aa1c-c320682d2c73";
/// <summary>
/// Initializes a new instance of the <see cref="TermWindow"/> class.
/// </summary>
public TermWindow(ToolWindowContext context) : base()
public TermWindow(TermWindowPackage package) : base(package)
{
this.Caption = "Terminal Window";
// This is the user control hosted by the tool window; Note that, even if this class implements IDisposable,
// we are not calling Dispose on this object. This is because ToolWindowPane calls Dispose on
// the object returned by the Content property.
this.Content = new TermWindowControl(context);
package.CreateTerminalAsync(
EmbeddedTerminalOptions.Default,
pane: this
)
.FileAndForget("WhackWhackTerminal/TerminalWindow/Open");
}
}
}

Просмотреть файл

@ -43,14 +43,14 @@ namespace Microsoft.VisualStudio.Terminal
// Get the instance number 0 of this tool window. This window is single instance so this instance
// is actually the only one.
// The last flag is set to true so that if the tool window does not exists it will be created.
package.JoinableTaskFactory.RunAsync(async () =>
{
ToolWindowPane window = await package.ShowToolWindowAsync(
package.JoinableTaskFactory.RunAsync(() =>
package.ShowToolWindowAsync(
typeof(TermWindow),
0,
create: true,
cancellationToken: package.DisposalToken);
}).FileAndForget("WhackWhackTerminal/TerminalWindow/Open");
cancellationToken: package.DisposalToken)
)
.FileAndForget("WhackWhackTerminal/TerminalWindow/Open");
}
}
}

Просмотреть файл

@ -1,4 +1,4 @@
<UserControl
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@ -9,7 +9,7 @@
Background="{DynamicResource VsBrush.Window}"
Foreground="{DynamicResource VsBrush.WindowText}"
x:Name="MyToolWindow">
<Grid x:Name="mainGrid">
<Grid x:Name="mainGrid">
<local:BetterBrowser x:Name="terminalView" />
</Grid>
</UserControl>

Просмотреть файл

@ -1,59 +1,109 @@
namespace Microsoft.VisualStudio.Terminal
{
using Microsoft.VisualStudio.Shell;
using System.IO;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System;
using StreamJsonRpc;
using Microsoft.VisualStudio.PlatformUI;
using Microsoft.VisualStudio.PlatformUI;
using System;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using static System.FormattableString;
namespace Microsoft.VisualStudio.Terminal
{
/// <summary>
/// Interaction logic for TermWindowControl.
/// Interaction logic for ServiceToolWindowControl.
/// </summary>
public partial class TermWindowControl : UserControl, IDisposable
{
private readonly TermWindowPackage package;
private bool pendingFocus;
/// <summary>
/// Initializes a new instance of the <see cref="TermWindowControl"/> class.
/// Initializes a new instance of the <see cref="ServiceToolWindowControl"/> class.
/// </summary>
public TermWindowControl(ToolWindowContext context)
public TermWindowControl(TermWindowPackage package)
{
this.package = package;
this.InitializeComponent();
this.package = context.Package;
this.Focusable = true;
this.GotFocus += TermWindowControl_GotFocus;
var target = new TerminalEvent(context.Package, this.terminalView, context.SolutionUtils);
var rpc = JsonRpc.Attach(context.ServiceHubStream, target);
context.SolutionUtils.SolutionChanged += SolutionUtils_SolutionChanged;
VSColorTheme.ThemeChanged += VSColorTheme_ThemeChanged;
this.terminalView.ScriptingObject = new TerminalScriptingObject(context.Package, rpc, context.SolutionUtils, null, true, null, null, null);
string extensionDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
string rootPath = Path.Combine(extensionDirectory, "WebView\\default.html").Replace("\\\\", "\\");
this.terminalView.Navigate(new Uri(rootPath));
this.LostFocus += TermWindowControl_LostFocus;
}
internal void PtyData(string data) => this.terminalView.Invoke("triggerEvent", "ptyData", data);
internal async Task InitAsync(TerminalScriptingObject scriptingObject, CancellationToken cancellationToken)
{
await this.package.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
var tcs = new TaskCompletionSource<object>();
void TerminalInitHandler(object sender, TermInitEventArgs e) => tcs.TrySetResult(null);
scriptingObject.TerminalInit += TerminalInitHandler;
try
{
VSColorTheme.ThemeChanged += VSColorTheme_ThemeChanged;
this.terminalView.ScriptingObject = scriptingObject;
string extensionDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
string rootPath = Path.Combine(extensionDirectory, "WebView\\default.html").Replace("\\\\", "\\");
this.terminalView.Navigate(new Uri(rootPath));
using (var registration = cancellationToken.Register(() => tcs.TrySetCanceled(cancellationToken)))
{
await tcs.Task;
}
}
finally
{
scriptingObject.TerminalInit -= TerminalInitHandler;
}
if (this.pendingFocus)
{
this.terminalView.Invoke("triggerEvent", "focus");
this.pendingFocus = false;
}
}
internal void Resize(int cols, int rows) => this.terminalView.Invoke(
"triggerEvent",
"resize",
Invariant($"{{\"cols\": {cols}, \"rows\": {rows}}}"));
internal void ChangeWorkingDirectory(string newDirectory) =>
this.terminalView.Invoke("triggerEvent", "directoryChanged", newDirectory);
public void Dispose()
{
var helper = (ITerminalScriptingObject)this.terminalView.ScriptingObject;
helper.ClosePty();
helper?.ClosePty();
}
internal void PtyExited(int? code) =>
this.terminalView.Invoke("triggerEvent", "ptyExited", code);
private void TermWindowControl_GotFocus(object sender, RoutedEventArgs e)
{
// We call focus here because if we don't, the next call will prevent the toolbar from turning blue.
// No functionality is lost when this happens but it is not consistent with VS design conventions.
this.Focus();
this.terminalView.Invoke("triggerEvent", "focus");
if (this.terminalView.HasDocument)
{
this.pendingFocus = false;
this.terminalView.Invoke("triggerEvent", "focus");
}
else
{
this.pendingFocus = true;
}
}
private void TermWindowControl_LostFocus(object sender, RoutedEventArgs e) =>
this.pendingFocus = false;
private void VSColorTheme_ThemeChanged(ThemeChangedEventArgs e)
{
this.package.JoinableTaskFactory.RunAsync(async () =>
@ -62,17 +112,5 @@
this.terminalView.Invoke("triggerEvent", "themeChanged", TerminalThemer.GetTheme());
});
}
private void SolutionUtils_SolutionChanged(object sender, string solutionDir)
{
if (this.package.OptionChangeDirectory)
{
this.package.JoinableTaskFactory.RunAsync(async () =>
{
await this.package.JoinableTaskFactory.SwitchToMainThreadAsync();
this.terminalView.Invoke("triggerEvent", "directoryChanged", solutionDir);
});
}
}
}
}

Просмотреть файл

@ -1,14 +1,12 @@
using System;
using Microsoft.ServiceHub.Client;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Terminal.VsService;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Terminal.VsService;
using Microsoft.ServiceHub.Client;
using Microsoft.VisualStudio.ComponentModelHost;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Workspace.VSIntegration.Contracts;
using Task = System.Threading.Tasks.Task;
namespace Microsoft.VisualStudio.Terminal
@ -36,7 +34,7 @@ namespace Microsoft.VisualStudio.Terminal
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "pkgdef, VS and vsixmanifest are valid VS terms")]
[ProvideMenuResource("Menus.ctmenu", 1)]
[ProvideToolWindow(typeof(TermWindow), Style = VsDockStyle.Tabbed, Window = "34E76E81-EE4A-11D0-AE2E-00A0C90FFFC3")]
[ProvideToolWindow(typeof(ServiceToolWindow), Transient = true, MultiInstances = true, Style = VsDockStyle.Tabbed, Window = TermWindow.TermWindowGuidString)]
[ProvideToolWindow(typeof(RendererWindow), Transient = true, MultiInstances = true, Style = VsDockStyle.Tabbed, Window = TermWindow.ToolWindowGuid)]
[ProvideOptionPage(typeof(TerminalOptionPage), "Whack Whack Terminal", "General", 0, 0, true)]
[ProvideService(typeof(STerminalService), IsAsyncQueryable = true)]
public sealed class TermWindowPackage : AsyncPackage
@ -47,6 +45,7 @@ namespace Microsoft.VisualStudio.Terminal
public const string PackageGuidString = "35b633bd-cdfb-4eda-8c26-f557e419eb8a";
private IVsSettingsManager settingsManager;
private int nextToolWindowId = 1;
/// <summary>
/// Initialization of the package; this method is called right after the package is sited, so this is the place
@ -55,56 +54,59 @@ namespace Microsoft.VisualStudio.Terminal
protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
{
await base.InitializeAsync(cancellationToken, progress);
this.AddService(typeof(STerminalService), CreateServiceAsync, promote: true);
await this.JoinableTaskFactory.SwitchToMainThreadAsync();
this.settingsManager = (IVsSettingsManager)await this.GetServiceAsync(typeof(SVsSettingsManager));
await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
this.settingsManager = (IVsSettingsManager)await this.GetServiceAsync(typeof(SVsSettingsManager));
await TermWindowCommand.InitializeCommandAsync(this);
}
private Task<object> CreateServiceAsync(IAsyncServiceContainer container, CancellationToken cancellationToken, Type serviceType)
private Task<object> CreateServiceAsync(IAsyncServiceContainer container, CancellationToken cancellationToken, Type serviceType) =>
Task.FromResult<object>(new EmbeddedTerminalService(this));
public override IVsAsyncToolWindowFactory GetAsyncToolWindowFactory(Guid toolWindowType) =>
toolWindowType == new Guid(TermWindow.ToolWindowGuid) || toolWindowType == new Guid(RendererWindow.ToolWindowGuid) ? this : null;
protected override string GetToolWindowTitle(Type toolWindowType, int id) =>
typeof(TermWindowPane).IsAssignableFrom(toolWindowType) ? "Terminal Window" : base.GetToolWindowTitle(toolWindowType, id);
protected override Task<object> InitializeToolWindowAsync(Type toolWindowType, int id, CancellationToken cancellationToken) =>
Task.FromResult(typeof(TermWindowPane).IsAssignableFrom(toolWindowType) ? this : ToolWindowCreationContext.Unspecified);
internal async Task<EmbeddedTerminal> CreateTerminalAsync(EmbeddedTerminalOptions options, TermWindowPane pane)
{
return Task.FromResult((object)new EmbeddedTerminalService(this));
var rpcStreamTask = JoinableTaskFactory.RunAsync(async () =>
{
var client = new HubClient();
return await client.RequestServiceAsync("wwt.pty", DisposalToken);
});
pane = pane ?? await CreateToolWindowAsync(options.Name);
var result = new EmbeddedTerminal(this, pane, options, rpcStreamTask.Task);
await result.InitAsync(DisposalToken);
return result;
}
public override IVsAsyncToolWindowFactory GetAsyncToolWindowFactory(Guid toolWindowType)
internal async Task<TerminalRenderer> CreateTerminalRendererAsync(string name)
{
if (toolWindowType.Equals(new Guid(TermWindow.TermWindowGuidString)) || toolWindowType.Equals(new Guid(ServiceToolWindow.ServiceToolWindowGuid)))
{
return this;
}
return null;
var pane = await CreateToolWindowAsync(name);
var result = new TerminalRenderer(this, pane);
await result.InitAsync(DisposalToken);
return result;
}
protected override string GetToolWindowTitle(Type toolWindowType, int id)
private async Task<RendererWindow> CreateToolWindowAsync(string name)
{
if (toolWindowType == typeof(TermWindow) || toolWindowType == typeof(ServiceToolWindow))
{
return "Terminal Window";
}
return base.GetToolWindowTitle(toolWindowType, id);
}
protected override async Task<object> InitializeToolWindowAsync(Type toolWindowType, int id, CancellationToken cancellationToken)
{
await this.JoinableTaskFactory.SwitchToMainThreadAsync();
var solutionService = (IVsSolution)await this.GetServiceAsync(typeof(SVsSolution));
var componentModel = (IComponentModel)await this.GetServiceAsync(typeof(SComponentModel));
var workspaceService = componentModel.GetService<IVsFolderWorkspaceService>();
var solutionUtils = new SolutionUtils(solutionService, workspaceService);
var client = new HubClient();
var clientStream = await client.RequestServiceAsync("wwt.pty");
return new ToolWindowContext()
{
Package = this,
ServiceHubStream = clientStream,
SolutionUtils = solutionUtils,
};
await JoinableTaskFactory.SwitchToMainThreadAsync(DisposalToken);
var pane = (RendererWindow)await FindToolWindowAsync(
typeof(RendererWindow),
nextToolWindowId++,
create: true,
cancellationToken: DisposalToken);
pane.Caption = name;
return pane;
}
public DefaultTerminal OptionTerminal

Просмотреть файл

@ -0,0 +1,23 @@
using Microsoft.VisualStudio.Shell;
using System.Threading;
using Task = System.Threading.Tasks.Task;
namespace Microsoft.VisualStudio.Terminal
{
/// <summary>
/// This class implements the tool window exposed by this package and hosts a user control.
/// </summary>
public class TermWindowPane : ToolWindowPane
{
/// <summary>
/// Initializes a new instance of the <see cref="TermWindow"/> class.
/// </summary>
public TermWindowPane(TermWindowPackage package) : base(null)
{
this.Content = new TermWindowControl(package);
}
internal Task InitAsync(TerminalScriptingObject scriptingObject, CancellationToken cancellationToken) =>
((TermWindowControl)Content).InitAsync(scriptingObject, cancellationToken);
}
}

Просмотреть файл

@ -1,25 +0,0 @@
using System.IO;
namespace Microsoft.VisualStudio.Terminal
{
public class ToolWindowContext
{
internal TermWindowPackage Package
{
set;
get;
}
internal SolutionUtils SolutionUtils
{
set;
get;
}
internal Stream ServiceHubStream
{
set;
get;
}
}
}

Просмотреть файл

@ -1,102 +1,169 @@
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.ComponentModelHost;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Threading;
using Microsoft.VisualStudio.Workspace.VSIntegration.Contracts;
using StreamJsonRpc;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Task = System.Threading.Tasks.Task;
namespace Microsoft.VisualStudio.Terminal.VsService
{
public class EmbeddedTerminal : ITerminal
internal sealed class EmbeddedTerminal : TerminalRenderer
{
private readonly TermWindowPackage package;
private readonly ServiceToolWindow windowPane;
private readonly EmbeddedTerminalOptions options;
private readonly AsyncLazy<JsonRpc> rpc;
private readonly AsyncLazy<SolutionUtils> solutionUtils;
private bool isRpcDisconnected;
public event EventHandler Closed;
public EmbeddedTerminal(TermWindowPackage package, ServiceToolWindow windowPane)
public EmbeddedTerminal(TermWindowPackage package, TermWindowPane pane, EmbeddedTerminalOptions options, Task<Stream> rpcStreamTask) : base(package, pane)
{
ThreadHelper.ThrowIfNotOnUIThread();
this.package = package;
this.windowPane = windowPane;
this.options = options;
this.rpc = new AsyncLazy<JsonRpc>(() => GetJsonRpcAsync(rpcStreamTask), package.JoinableTaskFactory);
if (this.windowPane.Frame is IVsWindowFrame2 windowFrame)
if (options.WorkingDirectory == null)
{
var events = new WindowFrameEvents(windowFrame, this);
windowFrame.Advise(events, out var cookie);
events.Cookie = cookie;
// Use solution directory
this.solutionUtils = new AsyncLazy<SolutionUtils>(GetSolutionUtilsAsync, package.JoinableTaskFactory);
// Start getting solution utils but don't block on the result.
// Solution utils need MEF and sometimes it takes long time to initialize.
this.solutionUtils.GetValueAsync().FileAndForget("WhackWhackTerminal/GetSolutionUtils");
}
}
public async Task CloseAsync()
protected internal override string SolutionDirectory =>
this.options.WorkingDirectory ?? this.package.JoinableTaskFactory.Run(() => GetSolutionDirectoryAsync(this.package.DisposalToken));
internal protected override void OnTerminalResized(int cols, int rows)
{
await this.package.JoinableTaskFactory.SwitchToMainThreadAsync();
(this.windowPane.Frame as IVsWindowFrame)?.CloseFrame((uint)__FRAMECLOSE.FRAMECLOSE_NoSave);
base.OnTerminalResized(cols, rows);
ResizeTermAsync(cols, rows).FileAndForget("WhackWhackTerminal/ResizePty");
}
public async Task HideAsync()
internal protected override void OnTerminalDataRecieved(string data)
{
await this.package.JoinableTaskFactory.SwitchToMainThreadAsync();
(this.windowPane.Frame as IVsWindowFrame)?.Hide();
base.OnTerminalDataRecieved(data);
SendTermDataAsync(data).FileAndForget("WhackWhackTerminal/TermData");
}
public async Task ShowAsync()
internal protected override void OnTerminalClosed()
{
await this.package.JoinableTaskFactory.SwitchToMainThreadAsync();
(this.windowPane.Frame as IVsWindowFrame)?.Show();
base.OnTerminalClosed();
CloseTermAsync().FileAndForget("WhackWhackTerminal/closeTerm");
}
public Task ChangeWorkingDirectoryAsync(string newDirectory)
protected override void OnClosed()
{
return ((ServiceToolWindowControl)this.windowPane.Content).ChangeWorkingDirectoryAsync(newDirectory);
base.OnClosed();
CloseRpcAsync().FileAndForget("WhackWhackTerminal/closeRpc");
}
private class WindowFrameEvents: IVsWindowFrameNotify, IVsWindowFrameNotify2
internal protected override void OnTerminalInit(object sender, TermInitEventArgs e)
{
private readonly IVsWindowFrame2 frame;
private EmbeddedTerminal terminal;
base.OnTerminalInit(sender, e);
InitTermAsync(e).FileAndForget("WhackWhackTerminal/InitPty");
}
public WindowFrameEvents(IVsWindowFrame2 frame, EmbeddedTerminal terminal)
private async Task<JsonRpc> GetJsonRpcAsync(Task<Stream> rpcStreamTask)
{
var stream = await rpcStreamTask;
var target = new TerminalEvent(this.package, this);
return JsonRpc.Attach(stream, target);
}
private async Task ResizeTermAsync(int cols, int rows)
{
var rpc = await this.rpc.GetValueAsync();
if (!this.isRpcDisconnected)
{
this.frame = frame;
await rpc.InvokeAsync("resizeTerm", cols, rows);
}
}
private async Task SendTermDataAsync(string data)
{
var rpc = await this.rpc.GetValueAsync();
if (!this.isRpcDisconnected)
{
await rpc.InvokeAsync("termData", data);
}
}
private async Task CloseTermAsync()
{
var rpc = await this.rpc.GetValueAsync();
if (!this.isRpcDisconnected)
{
await rpc.InvokeAsync("closeTerm");
}
}
private async Task CloseRpcAsync()
{
var rpc = await this.rpc.GetValueAsync();
rpc.Dispose();
this.isRpcDisconnected = true;
}
private async Task InitTermAsync(TermInitEventArgs e)
{
var rpc = await this.rpc.GetValueAsync();
if (!this.isRpcDisconnected)
{
var path = this.options.ShellPath ??
(this.package.OptionTerminal == DefaultTerminal.Other ? this.package.OptionShellPath : this.package.OptionTerminal.ToString());
var args = ((object)this.options.Args) ?? this.package.OptionStartupArgument;
await rpc.InvokeAsync("initTerm", path, e.Cols, e.Rows, e.Directory, args, this.options.Environment);
}
}
private async Task<SolutionUtils> GetSolutionUtilsAsync()
{
await this.package.JoinableTaskFactory.SwitchToMainThreadAsync(this.package.DisposalToken);
var solutionService = (IVsSolution)await this.package.GetServiceAsync(typeof(SVsSolution));
var componentModel = (IComponentModel)await this.package.GetServiceAsync(typeof(SComponentModel));
var workspaceService = componentModel.GetService<IVsFolderWorkspaceService>();
var result = new SolutionUtils(solutionService, workspaceService, this.package.JoinableTaskFactory);
result.SolutionChanged += (sender, solutionDir) =>
{
if (package.OptionChangeDirectory)
{
this.ChangeWorkingDirectoryAsync(solutionDir).FileAndForget("WhackWhackTerminal/changeWorkingDirectory");
}
};
return result;
}
private async Task<string> GetSolutionDirectoryAsync(CancellationToken cancellationToken)
{
var solutionUtils = await this.solutionUtils.GetValueAsync(cancellationToken);
return await solutionUtils.GetSolutionDirAsync(cancellationToken) ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
}
// RPC event target
internal sealed class TerminalEvent
{
private readonly TermWindowPackage package;
private readonly EmbeddedTerminal terminal;
public TerminalEvent(TermWindowPackage package, EmbeddedTerminal terminal)
{
this.package = package;
this.terminal = terminal;
}
public uint? Cookie
{
get;
set;
}
[JsonRpcMethod("PtyData")]
public Task PtyDataAsync(string data) =>
this.terminal.PtyDataAsync(data, this.package.DisposalToken);
public int OnClose(ref uint pgrfSaveOptions)
{
ThreadHelper.ThrowIfNotOnUIThread();
terminal.Closed?.Invoke(terminal, EventArgs.Empty);
if (this.Cookie.HasValue)
{
this.frame?.Unadvise(this.Cookie.Value);
}
return VSConstants.S_OK;
}
public int OnShow(int fShow)
{
return VSConstants.S_OK;
}
public int OnMove()
{
return VSConstants.S_OK;
}
public int OnSize()
{
return VSConstants.S_OK;
}
public int OnDockableChange(int fDockable)
{
return VSConstants.S_OK;
}
[JsonRpcMethod("PtyExit")]
public Task PtyExitAsync(int? code) =>
this.terminal.PtyExitedAsync(code, this.package.DisposalToken);
}
}
}

Просмотреть файл

@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace Microsoft.VisualStudio.Terminal.VsService
{
[DebuggerStepThrough]
internal sealed class EmbeddedTerminalOptions
{
private const string DefaultName = "Terminal Window";
public static EmbeddedTerminalOptions Default { get; } = new EmbeddedTerminalOptions();
public EmbeddedTerminalOptions()
{
Name = DefaultName;
}
public EmbeddedTerminalOptions(string name, string shellPath, string workingDirectory, IEnumerable<string> args, IDictionary<string, string> environment)
{
Name = name ?? DefaultName;
ShellPath = shellPath;
WorkingDirectory = workingDirectory;
Args = args?.ToArray();
Environment = environment == null ? null : new Dictionary<string, string>(environment, StringComparer.OrdinalIgnoreCase);
}
public string Name { get; }
public string ShellPath { get; }
public string WorkingDirectory { get; }
public IEnumerable<string> Args { get; }
public IDictionary<string, string> Environment { get; }
}
}

Просмотреть файл

@ -1,12 +1,10 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Shell;
namespace Microsoft.VisualStudio.Terminal.VsService
{
public class EmbeddedTerminalService : STerminalService, ITerminalService
public class EmbeddedTerminalService : STerminalService, ITerminalService, ITerminalRendererService
{
private int nextToolWindowId = 1;
private readonly TermWindowPackage package;
public EmbeddedTerminalService(TermWindowPackage package)
@ -14,18 +12,17 @@ namespace Microsoft.VisualStudio.Terminal.VsService
this.package = package;
}
public async Task<object> CreateTerminalAsync(string name, string shellPath, string workingDirectory, IEnumerable<string> args, IDictionary<string, string> environment)
{
await this.package.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken);
var pane = (ServiceToolWindow)await package.FindToolWindowAsync(
typeof(ServiceToolWindow),
nextToolWindowId++,
create: true,
cancellationToken: package.DisposalToken);
pane.Caption = name;
((ServiceToolWindowControl)pane.Content).FinishInitialize(workingDirectory, shellPath, args, environment);
public async Task<object> CreateTerminalAsync(string name, string shellPath, string workingDirectory, IEnumerable<string> args, IDictionary<string, string> environment) =>
await this.package.CreateTerminalAsync(new EmbeddedTerminalOptions(name, shellPath, workingDirectory, args, environment), pane: null);
return new EmbeddedTerminal(this.package, pane);
async Task<object> ITerminalRendererService.CreateTerminalRendererAsync(string name) =>
await this.package.CreateTerminalRendererAsync(name);
public async Task<object> CreateTerminalRendererAsync(string name, int cols, int rows)
{
var result = await this.package.CreateTerminalRendererAsync(name);
await result.ResizeAsync(cols, rows, package.DisposalToken);
return result;
}
}
}

Просмотреть файл

@ -0,0 +1,166 @@
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using System;
using System.Threading;
using System.Threading.Tasks;
using Task = System.Threading.Tasks.Task;
namespace Microsoft.VisualStudio.Terminal.VsService
{
internal class TerminalRenderer : ITerminal, ITerminalRenderer
{
private readonly TermWindowPane pane;
private readonly TerminalScriptingObject scriptingObject;
protected readonly TermWindowPackage package;
public TerminalRenderer(TermWindowPackage package, TermWindowPane pane)
{
ThreadHelper.ThrowIfNotOnUIThread();
this.package = package;
this.pane = pane;
if (this.pane.Frame is IVsWindowFrame2 windowFrame)
{
new WindowFrameEvents(windowFrame)
{
Closed = (sender, e) => OnClosed(),
};
}
this.scriptingObject = new TerminalScriptingObject(this, package);
this.scriptingObject.TerminalInit += OnTerminalInit;
}
public event EventHandler<TermInitEventArgs> TerminalInit;
public event EventHandler TerminalClosed;
public event Func<string> SolutionDirectoryProvider;
internal protected virtual string SolutionDirectory => null;
private TermWindowControl Control => ((TermWindowControl)this.pane.Content);
internal Task InitAsync(CancellationToken cancellationToken) =>
this.pane.InitAsync(this.scriptingObject, cancellationToken);
#region ITerminal
public async Task ShowAsync()
{
await this.package.JoinableTaskFactory.SwitchToMainThreadAsync();
(this.pane.Frame as IVsWindowFrame)?.Show();
}
public async Task HideAsync()
{
await this.package.JoinableTaskFactory.SwitchToMainThreadAsync();
(this.pane.Frame as IVsWindowFrame)?.Hide();
}
public async Task CloseAsync()
{
await this.package.JoinableTaskFactory.SwitchToMainThreadAsync();
(this.pane.Frame as IVsWindowFrame)?.CloseFrame((uint)__FRAMECLOSE.FRAMECLOSE_NoSave);
}
public async Task ChangeWorkingDirectoryAsync(string newDirectory)
{
await this.package.JoinableTaskFactory.SwitchToMainThreadAsync();
Control.ChangeWorkingDirectory(newDirectory);
}
public event EventHandler Closed;
#endregion
#region ITerminalRenderer
public int Cols { get; private set; }
public int Rows { get; private set; }
public event EventHandler TerminalResized;
public event TerminalDataRecievedEventHandler TerminalDataRecieved;
public async Task ResizeAsync(int cols, int rows, CancellationToken cancellationToken)
{
await this.package.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
Control.Resize(cols, rows);
}
public async Task PtyDataAsync(string data, CancellationToken cancellationToken)
{
await this.package.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
Control.PtyData(data);
}
#endregion
internal async Task PtyExitedAsync(int? code, CancellationToken cancellationToken)
{
await this.package.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
Control.PtyExited(code);
}
internal protected virtual void OnTerminalDataRecieved(string data) =>
TerminalDataRecieved?.Invoke(this, data);
internal protected virtual void OnTerminalResized(int cols, int rows)
{
bool changed = Cols != cols || Rows != rows;
Cols = cols;
Rows = rows;
if (changed)
{
TerminalResized?.Invoke(this, EventArgs.Empty);
}
}
internal protected virtual void OnTerminalInit(object sender, TermInitEventArgs e) =>
TerminalInit?.Invoke(this, e);
internal protected virtual void OnTerminalClosed() =>
TerminalClosed?.Invoke(this, EventArgs.Empty);
internal string ScriptingObject_GetSolutionDirectory()
{
var provider = SolutionDirectoryProvider;
return provider != null ? provider() : null;
}
protected virtual void OnClosed() =>
Closed?.Invoke(this, EventArgs.Empty);
private sealed class WindowFrameEvents : IVsWindowFrameNotify, IVsWindowFrameNotify2
{
private readonly IVsWindowFrame2 frame;
private readonly uint cookie;
public WindowFrameEvents(IVsWindowFrame2 frame)
{
ThreadHelper.ThrowIfNotOnUIThread();
this.frame = frame;
ErrorHandler.ThrowOnFailure(frame.Advise(this, out this.cookie));
}
public EventHandler Closed
{
get;
set;
}
public int OnClose(ref uint pgrfSaveOptions)
{
ThreadHelper.ThrowIfNotOnUIThread();
Closed?.Invoke(this, EventArgs.Empty);
this.frame.Unadvise(this.cookie);
return VSConstants.S_OK;
}
public int OnShow(int fShow) => VSConstants.S_OK;
public int OnMove() => VSConstants.S_OK;
public int OnSize() => VSConstants.S_OK;
public int OnDockableChange(int fDockable) => VSConstants.S_OK;
}
}
}

Просмотреть файл

@ -1,5 +1,5 @@
import { ITheme, Terminal } from 'xterm';
import { fit } from 'xterm/lib/addons/fit';
import { apply as applyFit } from 'xterm/lib/addons/fit';
import { VisualStudio } from './VsEventManager';
import { registerLocalLinkHandler } from './TerminalLinkMatcher';
@ -10,11 +10,21 @@ import * as xterm from 'xterm';
const TerminalConstructor = xterm as any as (typeof Terminal);
export class TermView {
private autoFit: boolean;
private term: Terminal;
private termFit: TerminalFit;
private resizeTimeout: number | null;
private solutionDirectory: string;
private borderStyle: HTMLElement;
private backgroundColor: string;
private borderColor: string;
constructor(theme: ITheme, fontFamily: string, fontSize: number, solutionDirectory: string) {
this.solutionDirectory = solutionDirectory;
this.autoFit = true;
applyFit(TerminalConstructor);
this.term = new TerminalConstructor({
theme: theme,
fontFamily: fontFamily + ', courier-new, courier, monospace',
@ -23,9 +33,19 @@ export class TermView {
cols: 80,
rows: 24
});
this.termFit = <any>this.term;
this.term.open(document.getElementById('content'));
fit(this.term);
this.backgroundColor = theme.background;
this.borderColor = (<any>theme).border || theme.foreground;
const content = document.getElementById('content');
this.borderStyle = document.createElement('style');
this.updateBorder();
content.appendChild(this.borderStyle);
this.term.open(content);
this.termFit.fit();
this.term.on('data', (data) => this.termData(data));
VisualStudio.Events.on('ptyData', (data) => this.ptyData(data));
VisualStudio.Events.on('themeChanged', (data) => {
@ -48,12 +68,31 @@ export class TermView {
VisualStudio.Events.on('focus', () => {
this.term.focus();
});
VisualStudio.Events.on('resize', data => {
const size: Geometry = JSON.parse(data);
if (size) {
const borderChanging = this.autoFit;
this.autoFit = false;
if (borderChanging) {
this.updateBorder();
}
this.resizeTerm(size);
}
});
window.addEventListener("resize", () => this.resizeHandler())
this.registerKeyboardHandlers();
this.initPty(solutionDirectory);
registerLocalLinkHandler(this.term);
}
private resizeTerm(size: Geometry) {
if (this.term.cols !== size.cols || this.term.rows !== size.rows) {
(<any>this.term).renderer.clear();
this.term.resize(size.cols, size.rows);
}
}
private initPty(cwd: string) {
window.external.InitPty(this.term.cols, this.term.rows, cwd);
}
@ -72,12 +111,21 @@ export class TermView {
private setTheme(theme: ITheme) {
this.term.setOption('theme', theme);
this.backgroundColor = theme.background;
this.borderColor = (<any>theme).border || theme.foreground;
this.updateBorder();
}
private resizeHandler() {
let actualHandler = () => {
fit(this.term);
window.external.ResizePty(this.term.cols, this.term.rows);
const size: Geometry = this.termFit.proposeGeometry();
if (size) {
if (this.autoFit) {
this.resizeTerm(size);
}
window.external.ResizePty(size.cols, size.rows);
}
};
let timeoutCallback = () => {
@ -113,4 +161,16 @@ export class TermView {
}
});
}
private updateBorder() {
this.borderStyle.innerHTML = this.autoFit ? '' :
'.xterm-screen { ' +
'border-style: dotted; ' +
'border-width: 1px; ' +
`border-top-color: ${this.backgroundColor}; ` +
`border-bottom-color: ${this.borderColor}; ` +
`border-left-color: ${this.backgroundColor}; ` +
`border-right-color: ${this.borderColor}` +
'}';
}
}

11
Microsoft.VisualStudio.Terminal/WebView/ts/TerminalFit.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,11 @@
// This interface is applied to Terminal by fit's apply()
interface TerminalFit {
fit(): void;
proposeGeometry(): Geometry;
}
interface Geometry {
cols: number;
rows: number;
}

Просмотреть файл

@ -4,7 +4,8 @@
ptyData: '',
ptyExited: '',
directoryChanged: '',
focus: ''
focus: '',
resize: '',
};
export type EventType = keyof typeof eventStrings;

Просмотреть файл

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.ServiceHub.Client" version="1.0.226-rc" targetFramework="net46" />
<package id="Microsoft.TypeScript.MSBuild" version="2.7.1" targetFramework="net46" developmentDependency="true" />
<package id="Microsoft.TypeScript.MSBuild" version="2.8.3" targetFramework="net46" developmentDependency="true" />
<package id="Microsoft.VisualStudio.CoreUtility" version="15.6.27413" targetFramework="net46" />
<package id="Microsoft.VisualStudio.ImageCatalog" version="15.6.27413" targetFramework="net46" />
<package id="Microsoft.VisualStudio.Imaging" version="15.6.27413" targetFramework="net46" />

3
package-lock.json сгенерированный Normal file
Просмотреть файл

@ -0,0 +1,3 @@
{
"lockfileVersion": 1
}