Add TerminalRenderer API
This commit is contained in:
Родитель
6cb23f2b29
Коммит
f7fbdb4885
|
@ -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}` +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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" />
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"lockfileVersion": 1
|
||||
}
|
Загрузка…
Ссылка в новой задаче