This commit is contained in:
Andrey Shchekin 2016-09-10 22:04:16 +12:00
Коммит 807fa56928
32 изменённых файлов: 1182 добавлений и 0 удалений

14
.editorconfig Normal file
Просмотреть файл

@ -0,0 +1,14 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
insert_final_newline = false
[*.{html,config,csproj}]
indent_size = 2
[project.json]
indent_size = 2

6
.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,6 @@
*.user
project.lock.json
.vs/
**/wwwroot/external/
bin/
obj/

46
MirrorSharp.sln Normal file
Просмотреть файл

@ -0,0 +1,46 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{ED635736-B827-4C88-80B9-EBBE1DDADF12}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F358FD73-4BC9-42AA-BC4B-38DDB81FE813}"
ProjectSection(SolutionItems) = preProject
global.json = global.json
EndProjectSection
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "MirrorSharp", "src\MirrorSharp\MirrorSharp.xproj", "{C130C962-D17F-4741-B2B4-D74263BF380A}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "MirrorSharp.Demo", "src\MirrorSharp.Demo\MirrorSharp.Demo.xproj", "{70BCCC0E-6EEF-40D0-932A-87F9C42BD67B}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "MirrorSharp.Tests", "src\MirrorSharp.Tests\MirrorSharp.Tests.xproj", "{09F91A91-3DB9-44FF-A65E-5DB5A3F3A7A5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C130C962-D17F-4741-B2B4-D74263BF380A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C130C962-D17F-4741-B2B4-D74263BF380A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C130C962-D17F-4741-B2B4-D74263BF380A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C130C962-D17F-4741-B2B4-D74263BF380A}.Release|Any CPU.Build.0 = Release|Any CPU
{70BCCC0E-6EEF-40D0-932A-87F9C42BD67B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{70BCCC0E-6EEF-40D0-932A-87F9C42BD67B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{70BCCC0E-6EEF-40D0-932A-87F9C42BD67B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{70BCCC0E-6EEF-40D0-932A-87F9C42BD67B}.Release|Any CPU.Build.0 = Release|Any CPU
{09F91A91-3DB9-44FF-A65E-5DB5A3F3A7A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{09F91A91-3DB9-44FF-A65E-5DB5A3F3A7A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{09F91A91-3DB9-44FF-A65E-5DB5A3F3A7A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{09F91A91-3DB9-44FF-A65E-5DB5A3F3A7A5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{C130C962-D17F-4741-B2B4-D74263BF380A} = {ED635736-B827-4C88-80B9-EBBE1DDADF12}
{70BCCC0E-6EEF-40D0-932A-87F9C42BD67B} = {ED635736-B827-4C88-80B9-EBBE1DDADF12}
{09F91A91-3DB9-44FF-A65E-5DB5A3F3A7A5} = {ED635736-B827-4C88-80B9-EBBE1DDADF12}
EndGlobalSection
EndGlobal

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

@ -0,0 +1,12 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ACCESSOR_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ACCESSOR_OWNER_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/CASE_BLOCK_BRACES/@EntryValue">END_OF_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INITIALIZER_BRACES/@EntryValue">END_OF_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INVOCABLE_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/OTHER_BRACES/@EntryValue">END_OF_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/TYPE_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

6
global.json Normal file
Просмотреть файл

@ -0,0 +1,6 @@
{
"projects": [ "src", "test" ],
"sdk": {
"version": "1.0.0-preview2-003121"
}
}

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

@ -0,0 +1,3 @@
{
"directory": "wwwroot/external"
}

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

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>70bccc0e-6eef-40d0-932a-87f9c42bd67b</ProjectGuid>
<RootNamespace>MirrorSharp.Demo</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<ItemGroup>
<DnxInvisibleContent Include="bower.json" />
<DnxInvisibleContent Include=".bowerrc" />
</ItemGroup>
<Import Project="$(VSToolsPath)\DotNet.Web\Microsoft.DotNet.Web.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

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

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
namespace MirrorSharp.Demo
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
}
}

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

@ -0,0 +1,187 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Welcome to ASP.NET Core</title>
<style>
html {
background: #f1f1f1;
height: 100%;
}
body {
background: #fff;
color: #505050;
font: 14px 'Segoe UI', tahoma, arial, helvetica, sans-serif;
margin: 1%;
min-height: 95.5%;
border: 1px solid silver;
position: relative;
}
#header {
padding: 0;
}
#header h1 {
font-size: 44px;
font-weight: normal;
margin: 0;
padding: 10px 30px 10px 30px;
}
#header span {
margin: 0;
padding: 0 30px;
display: block;
}
#header p {
font-size: 20px;
color: #fff;
background: #007acc;
padding: 0 30px;
line-height: 50px;
margin-top: 25px;
}
#header p a {
color: #fff;
text-decoration: underline;
font-weight: bold;
padding-right: 35px;
background: no-repeat right bottom url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAAWCAMAAAAcqPc3AAAANlBMVEUAAAAAeswfitI9mthXp91us+KCvuaTx+mjz+2x1u+83PLH4vTR5/ba7Pjj8Pns9fv1+v3////wy3dWAAAAAXRSTlMAQObYZgAAAHxJREFUeNp9kVcSwCAIRMHUYoH7XzaxOxJ9P8oyQ1uIqNPwh3s2aLmIM2YtqrLcQIeQEylhuCeUOlhgve5yoBCfWmlnlgkN4H8ykbpaE7gR03AbUHiwoOxUH9Xp+ubd41p1HF3mBPrfC87BHeTdaB3ceeKL9HGpcvX9zu6+DdMWT9KQPvYAAAAASUVORK5CYII=);
}
#main {
padding: 5px 30px;
clear: both;
}
.section {
width: 21.7%;
float: left;
margin: 0 0 0 4%;
}
.section h2 {
font-size: 13px;
text-transform: uppercase;
margin: 0;
border-bottom: 1px solid silver;
padding-bottom: 12px;
margin-bottom: 8px;
}
.section.first {
margin-left: 0;
}
.section.first h2 {
font-size: 24px;
text-transform: none;
margin-bottom: 25px;
border: none;
}
.section.first li {
border-top: 1px solid silver;
padding: 8px 0;
}
.section.last {
margin-right: 0;
}
ul {
list-style: none;
padding: 0;
margin: 0;
line-height: 20px;
}
li {
padding: 4px 0;
}
a {
color: #267cb2;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
#footer {
clear: both;
padding-top: 50px;
}
#footer p {
position: absolute;
bottom: 10px;
}
</style>
</head>
<body>
<div id="header">
<h1>Welcome to ASP.NET Core</h1>
<span>
We've made some big updates in this release, so its <b>important</b> that you spend
a few minutes to learn whats new.
</span>
<p>You've created a new ASP.NET Core project. <a href="http://go.microsoft.com/fwlink/?LinkId=518016">Learn what's new</a></p>
</div>
<div id="main">
<div class="section first">
<h2>This application consists of:</h2>
<ul>
<li>Sample pages using ASP.NET Core MVC</li>
<li><a href="http://go.microsoft.com/fwlink/?LinkId=518004">Bower</a> for managing client-side libraries</li>
<li>Theming using <a href="http://go.microsoft.com/fwlink/?LinkID=398939">Bootstrap</a></li>
</ul>
</div>
<div class="section">
<h2>How to</h2>
<ul>
<li><a href="http://go.microsoft.com/fwlink/?LinkID=398600">Add a Controller and View</a></li>
<li><a href="http://go.microsoft.com/fwlink/?LinkID=699562">Add an appsetting in config and access it in app.</a></li>
<li><a href="http://go.microsoft.com/fwlink/?LinkId=699315">Manage User Secrets using Secret Manager.</a></li>
<li><a href="http://go.microsoft.com/fwlink/?LinkId=699316">Use logging to log a message.</a></li>
<li><a href="http://go.microsoft.com/fwlink/?LinkId=699317">Add packages using NuGet.</a></li>
<li><a href="http://go.microsoft.com/fwlink/?LinkId=699318">Add client packages using Bower.</a></li>
<li><a href="http://go.microsoft.com/fwlink/?LinkId=699319">Target development, staging or production environment.</a></li>
</ul>
</div>
<div class="section">
<h2>Overview</h2>
<ul>
<li><a href="http://go.microsoft.com/fwlink/?LinkId=518008">Conceptual overview of what is ASP.NET Core</a></li>
<li><a href="http://go.microsoft.com/fwlink/?LinkId=699320">Fundamentals of ASP.NET Core such as Startup and middleware.</a></li>
<li><a href="http://go.microsoft.com/fwlink/?LinkId=398602">Working with Data</a></li>
<li><a href="http://go.microsoft.com/fwlink/?LinkId=398603">Security</a></li>
<li><a href="http://go.microsoft.com/fwlink/?LinkID=699321">Client side development</a></li>
<li><a href="http://go.microsoft.com/fwlink/?LinkID=699322">Develop on different platforms</a></li>
<li><a href="http://go.microsoft.com/fwlink/?LinkID=699323">Read more on the documentation site</a></li>
</ul>
</div>
<div class="section last">
<h2>Run & Deploy</h2>
<ul>
<li><a href="http://go.microsoft.com/fwlink/?LinkID=517851">Run your app</a></li>
<li><a href="http://go.microsoft.com/fwlink/?LinkID=517853">Run tools such as EF migrations and more</a></li>
<li><a href="http://go.microsoft.com/fwlink/?LinkID=398609">Publish to Microsoft Azure Web Apps</a></li>
</ul>
</div>
<div id="footer">
<p>We would love to hear your <a href="http://go.microsoft.com/fwlink/?LinkId=518015">feedback</a></p>
</div>
</div>
</body>
</html>

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

@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:57835/",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"MirrorSharp.Demo": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

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

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace MirrorSharp.Demo {
public class Startup {
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services) {
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) {
loggerFactory.AddConsole();
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
app.UseDefaultFiles()
.UseStaticFiles()
.UseWebSockets()
.UseMirrorSharp();
}
}
}

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

@ -0,0 +1,14 @@
{
"name": "mirrorsharp-demo",
"authors": [
"Andrey Shchekin <ashmind@gmail.com>"
],
"description": "",
"main": "",
"license": "MIT",
"homepage": "",
"private": true,
"dependencies": {
"codemirror": "^5.18.2"
}
}

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

@ -0,0 +1,50 @@
{
"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.0.0",
"type": "platform"
},
"Microsoft.AspNetCore.Diagnostics": "1.0.0",
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
"Microsoft.Extensions.Logging.Console": "1.0.0",
"MirrorSharp": "*",
"Microsoft.AspNetCore.StaticFiles": "1.0.0",
"Microsoft.AspNetCore.WebSockets.Server": "0.1.0"
},
"tools": {
"Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final"
},
"frameworks": {
"netcoreapp1.0": {
"imports": [
"dotnet5.6",
"portable-net45+win8"
]
}
},
"buildOptions": {
"emitEntryPoint": true,
"preserveCompilationContext": true
},
"runtimeOptions": {
"configProperties": {
"System.GC.Server": true
}
},
"publishOptions": {
"include": [
"wwwroot",
"web.config"
]
},
"scripts": {
"postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
}
}

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

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<!--
Configure your application settings in appsettings.json. Learn more at http://go.microsoft.com/fwlink/?LinkId=786380
-->
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified"/>
</handlers>
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false"/>
</system.webServer>
</configuration>

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

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>MirrorSharp Demo</title>
<link rel="stylesheet" href="external/codemirror/lib/codemirror.css" />
<link rel="stylesheet" href="external/codemirror/addon/lint/lint.css" />
<link rel="stylesheet" href="external/codemirror/addon/hint/show-hint.css" />
</head>
<body>
<textarea></textarea>
<script src="external/codemirror/lib/codemirror.js"></script>
<script src="external/codemirror/addon/lint/lint.js"></script>
<script src="external/codemirror/addon/hint/show-hint.js"></script>
<script src="js/mirrorsharp.js"></script>
<script type="text/javascript">
mirrorsharp(document.getElementsByTagName('textarea')[0], {
serviceUrl: window.location.href.replace(/^http(.+)\/?$/i, 'ws$1/mirrorsharp')
});
</script>
</body>
</html>

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

@ -0,0 +1,77 @@
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define(['CodeMirror'], factory);
} else if (typeof module === 'object' && module.exports) {
module.exports = factory(require('CodeMirror'));
} else {
root.mirrorsharp = factory(root.CodeMirror);
}
}(this, function (CodeMirror) {
function getCursorIndex(cm) {
return cm.indexFromPos(cm.getCursor());
}
function showCompletions(cm, completions) {
cm.showHint({
hint: function () {
return { list: completions };
},
completeSingle: false
});
}
return function(textarea, options) {
const socket = new WebSocket(options.serviceUrl);
const cmOptions = options.forCodeMirror || { gutters: [] };
//cmOptions.lint = { async: true, getAnnotations: lint };
cmOptions.gutters.push('CodeMirror-lint-markers');
const cm = CodeMirror.fromTextArea(textarea, cmOptions);
const indexKey = '$$mirrorsharp_index$$';
var changePending = false;
cm.on('beforeChange', function(s, change) {
change.from[indexKey] = cm.indexFromPos(change.from);
change.to[indexKey] = cm.indexFromPos(change.to);
changePending = true;
});
cm.on('cursorActivity', function() {
if (changePending)
return;
const cursorIndex = getCursorIndex(cm);
socket.send('C' + cursorIndex);
});
cm.on('changes', function(s, changes) {
const cursorIndex = getCursorIndex(cm);
changePending = false;
for (var change of changes) {
const start = change.from[indexKey];
const length = change.to[indexKey] - start;
const text = change.text;
var message;
if (cursorIndex === start + 1 && text.length === 1) {
// typed a character
message = 'T' + text;
}
else {
// everything else
message = 'R' + start + ':' + length + ':' + cursorIndex + ':' + text;
}
socket.send(message);
}
});
socket.addEventListener('message', function (e) {
const message = JSON.parse(e.data);
switch (message.type) {
case 'completions':
showCompletions(cm, message.completions);
break;
}
});
}
}));

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

@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MirrorSharp.Internal;
using Moq;
using Xunit;
namespace MirrorSharp.Tests {
public class ConnectionTests {
[Theory]
[InlineData("C1", 1)]
[InlineData("C79", 79)]
[InlineData("C1234567890", 1234567890)]
public async void ReceiveAndProcessAsync_CallsMoveCursorOnSession_AfterReceivingMoveCursorCommand(string command, int expectedPosition) {
var socketMock = Mock.Of<WebSocket>();
SetupReceive(socketMock, command);
var sessionMock = Mock.Of<IWorkSession>();
await new Connection(socketMock, sessionMock).ReceiveAndProcessAsync();
Mock.Get(sessionMock).Verify(s => s.MoveCursor(expectedPosition));
}
[Theory]
[InlineData("T1", '1')]
[InlineData("Ta", 'a')]
[InlineData("T\u0216", '\u0216')]
[InlineData("T月", '月')]
public async void ReceiveAndProcessAsync_CallsTypeCharAsyncOnSession_AfterReceivingTypeCharCommand(string command, char expectedChar) {
var socketMock = Mock.Of<WebSocket>();
SetupReceive(socketMock, command);
var sessionMock = Mock.Of<IWorkSession>();
await new Connection(socketMock, sessionMock).ReceiveAndProcessAsync();
Mock.Get(sessionMock).Verify(s => s.TypeCharAsync(expectedChar));
}
[Theory]
[InlineData("R1:10:1:text", 1, 10, 1, "text")]
[InlineData("R1:10:1:", 1, 10, 1, "")]
[InlineData("R1:1:1:t:e:xt", 1, 1, 1, "t:e:xt")]
public async void ReceiveAndProcessAsync_CallsReplaceTextOnSession_AfterReceivingReplaceCommand(string command, int expectedStart, int expectedLength, int expectedPosition, string expectedText) {
var socketMock = Mock.Of<WebSocket>();
SetupReceive(socketMock, command);
var sessionMock = Mock.Of<IWorkSession>();
await new Connection(socketMock, sessionMock).ReceiveAndProcessAsync();
Mock.Get(sessionMock).Verify(s => s.ReplaceText(expectedStart, expectedLength, expectedText, expectedPosition));
}
private static void SetupReceive(WebSocket socket, string command) {
Mock.Get(socket)
.Setup(m => m.ReceiveAsync(It.IsAny<ArraySegment<byte>>(), It.IsAny<CancellationToken>()))
.Returns((ArraySegment<byte> s, CancellationToken _) => {
var byteCount = Encoding.UTF8.GetBytes(command.ToCharArray(), 0, command.Length, s.Array, s.Offset);
return Task.FromResult(new WebSocketReceiveResult(byteCount, WebSocketMessageType.Text, true));
});
}
}
}

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

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>09f91a91-3db9-44ff-a65e-5db5a3f3a7a5</ProjectGuid>
<RootNamespace>MirrorSharp.Tests</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

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

@ -0,0 +1,19 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("MirrorSharp.Tests")]
[assembly: AssemblyTrademark("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("09f91a91-3db9-44ff-a65e-5db5a3f3a7a5")]

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

@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MirrorSharp.Internal;
using Xunit;
namespace MirrorSharp.Tests {
public class SessionTests {
[Fact]
public async Task TypeChar_ProducesExpectedCompletion() {
var session = SessionFromTextWithCursor(@"
class A { public int x; }
class B { void M(A a) { a| } }
");
var result = await session.TypeCharAsync('.');
Assert.Equal(
new[] { "x" },
result.Completions.Items.Select(i => i.DisplayText)
);
}
private WorkSession SessionFromTextWithCursor(string textWithCursor) {
var cursorPosition = textWithCursor.LastIndexOf('|');
var text = textWithCursor.Remove(cursorPosition, 1);
var session = new WorkSession();
session.ReplaceText(0, 0, text, cursorPosition);
return session;
}
}
}

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

@ -0,0 +1,24 @@
{
"version": "1.0.0-*",
"testRunner": "xunit",
"dependencies": {
"NETStandard.Library": "1.6.0",
"xunit": "2.2.0-beta2-build3300",
"dotnet-test-xunit": "2.2.0-preview2-build1029",
"Moq": "4.6.38-alpha",
"MirrorSharp": "*"
},
"frameworks": {
"netcoreapp1.0": {
"imports": [ "dnxcore50", "portable-net45+win8+wp8+wpa81", "portable-net45+win8" ],
"dependencies": {
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.0.0"
}
}
}
}
}

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

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using MirrorSharp.Internal;
namespace MirrorSharp {
public static class AppBuilderExtensions {
public static void UseMirrorSharp(this IApplicationBuilder app) {
app.UseMiddleware<Middleware>();
}
}
}

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

@ -0,0 +1,154 @@
using System;
using System.IO;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace MirrorSharp.Internal {
public class Connection : IAsyncDisposable {
private static class Commands {
public const byte MoveCursor = (byte)'C';
public const byte Replace = (byte)'R';
public const byte TypeChar = (byte)'T';
}
private readonly WebSocket _socket;
private readonly IWorkSession _session;
private readonly byte[] _inputByteBuffer = new byte[2048];
private readonly byte[] _outputByteBuffer = new byte[2048];
private readonly char[] _charBuffer = new char[2048];
private readonly JsonSerializer _jsonSerializer = new JsonSerializer();
private readonly MemoryStream _jsonOutputStream;
private readonly JsonWriter _jsonOutputWriter;
public Connection(WebSocket socket, IWorkSession session) {
_socket = socket;
_session = session;
_jsonOutputStream = new MemoryStream(_outputByteBuffer);
_jsonOutputWriter = new JsonTextWriter(new StreamWriter(_jsonOutputStream));
}
public bool IsConnected => _socket.State == WebSocketState.Open;
public async Task ReceiveAndProcessAsync() {
try {
await ReceiveAndProcessInternalAsync().ConfigureAwait(false);
}
catch (Exception ex) {
try {
await SendTextAsync("RM-ERR-??: " + ex.Message).ConfigureAwait(false);
}
catch (Exception sendException) {
throw new AggregateException(ex, sendException);
}
throw;
}
}
private async Task ReceiveAndProcessInternalAsync() {
var received = await _socket.ReceiveAsync(new ArraySegment<byte>(_inputByteBuffer), CancellationToken.None).ConfigureAwait(false);
if (received.MessageType == WebSocketMessageType.Binary)
throw new FormatException("Expected text data (received binary).");
if (received.MessageType == WebSocketMessageType.Close)
return;
await ProcessMessageAsync(new ArraySegment<byte>(_inputByteBuffer, 0, received.Count)).ConfigureAwait(false);
}
private Task ProcessMessageAsync(ArraySegment<byte> data) {
var command = data.Array[data.Offset];
switch (command) {
case Commands.Replace: {
ProcessReplace(Shift(data));
return Task.CompletedTask;
}
case Commands.MoveCursor: {
ProcessMoveCursor(Shift(data));
return Task.CompletedTask;
}
case Commands.TypeChar: return ProcessTypeCharAsync(Shift(data));
default: throw new FormatException($"Unknown command: '{(char)command}'.");
}
}
private ArraySegment<byte> Shift(ArraySegment<byte> data) {
return new ArraySegment<byte>(data.Array, data.Offset + 1, data.Count - 1);
}
private void ProcessReplace(ArraySegment<byte> data) {
var endOffset = data.Offset + data.Count - 1;
var partStart = data.Offset;
int? start = null;
int? length = null;
int? cursorPosition = null;
for (var i = data.Offset; i <= endOffset; i++) {
if (data.Array[i] != (byte)':')
continue;
var part = new ArraySegment<byte>(data.Array, partStart, i - partStart);
if (start == null) {
start = FastConvert.Utf8ByteArrayToInt32(part);
partStart = i + 1;
continue;
}
if (length == null) {
length = FastConvert.Utf8ByteArrayToInt32(part);
partStart = i + 1;
continue;
}
cursorPosition = FastConvert.Utf8ByteArrayToInt32(part);
partStart = i + 1;
break;
}
if (start == null || length == null || cursorPosition == null)
throw new Exception("Command 'R' must be in a format 'Rstart:length:cursor:text'.");
var text = Encoding.UTF8.GetString(data.Array, partStart, endOffset - partStart + 1);
_session.ReplaceText(start.Value, length.Value, text, cursorPosition.Value);
}
private void ProcessMoveCursor(ArraySegment<byte> data) {
var cursorPosition = FastConvert.Utf8ByteArrayToInt32(data);
_session.MoveCursor(cursorPosition);
}
private async Task ProcessTypeCharAsync(ArraySegment<byte> data) {
var @char = FastConvert.Utf8ByteArrayToChar(data, _charBuffer);
var result = await _session.TypeCharAsync(@char).ConfigureAwait(false);
if (result.Completions == null)
return;
await SendJsonAsync(new {
type = "completions",
completions = result.Completions.Items.Select(i => $"[{string.Join(",", i.Tags)}] {i.DisplayText}")
}).ConfigureAwait(false);
}
private Task SendJsonAsync(object value) {
_jsonOutputStream.Seek(0, SeekOrigin.Begin);
_jsonSerializer.Serialize(_jsonOutputWriter, value);
_jsonOutputWriter.Flush();
return SendOutputBufferAsync((int)_jsonOutputStream.Position);
}
private Task SendTextAsync(string text) {
var byteCount = Encoding.UTF8.GetBytes(text, 0, text.Length, _outputByteBuffer, 0);
return SendOutputBufferAsync(byteCount);
}
private Task SendOutputBufferAsync(int byteCount) {
return _socket.SendAsync(new ArraySegment<byte>(_outputByteBuffer, 0, byteCount), WebSocketMessageType.Text, true, CancellationToken.None);
}
public Task DisposeAsync() => _session.DisposeAsync();
}
}

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

@ -0,0 +1,49 @@
using System;
using System.Linq;
using System.Text;
namespace MirrorSharp.Internal {
internal static class FastConvert {
private const byte Utf8Zero = (byte)'0';
private const byte Utf8Nine = (byte)'9';
private static readonly string[] CharStringMap =
Enumerable.Range(0, 128).Select(c => ((char)c).ToString()).ToArray();
public static int Utf8ByteArrayToInt32(ArraySegment<byte> bytes) {
var result = 0;
var array = bytes.Array;
var count = bytes.Offset + bytes.Count;
for (var i = bytes.Offset; i < count; i++) {
var @byte = array[i];
if (@byte < Utf8Zero || @byte > Utf8Nine)
throw new FormatException($"String '{SlowUtf8ByteArrayToString(bytes)}' is not a valid positive number.");
result = (10 * result) + (@byte - Utf8Zero);
}
return result;
}
public static char Utf8ByteArrayToChar(ArraySegment<byte> bytes, char[] buffer) {
if (bytes.Count == 1)
return (char)bytes.Array[bytes.Offset];
var charCount = Encoding.UTF8.GetChars(bytes.Array, bytes.Offset, bytes.Count, buffer, 0);
if (charCount != 1)
throw new FormatException($"Expected one char, but conversion produced {charCount}.");
return buffer[0];
}
public static string CharToString(char c) {
if (c <= 127)
return CharStringMap[c];
return c.ToString();
}
private static string SlowUtf8ByteArrayToString(ArraySegment<byte> bytes) {
return Encoding.UTF8.GetString(bytes.Array, bytes.Offset, bytes.Count);
}
}
}

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

@ -0,0 +1,7 @@
using System.Threading.Tasks;
namespace MirrorSharp.Internal {
public interface IAsyncDisposable {
Task DisposeAsync();
}
}

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

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace MirrorSharp.Internal {
public interface IWorkSession : IAsyncDisposable {
void ReplaceText(int start, int length, string newText, int cursorPositionAfter);
void MoveCursor(int cursorPosition);
Task<TypeCharResult> TypeCharAsync(char @char);
}
}

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

@ -0,0 +1,53 @@
using System;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Http;
namespace MirrorSharp.Internal {
public class Middleware {
private readonly RequestDelegate _next;
public Middleware(RequestDelegate next) {
_next = next;
}
[UsedImplicitly]
public async Task Invoke(HttpContext context) {
if (!context.WebSockets.IsWebSocketRequest) {
await _next(context);
return;
}
using (var socket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false)) {
var output = new byte[2048];
WorkSession session = null;
Connection connection = null;
try {
session = new WorkSession();
connection = new Connection(socket, session);
while (connection.IsConnected) {
try {
await connection.ReceiveAndProcessAsync();
}
catch {
// this is sent back by connection itself
}
}
}
catch (Exception) when(connection == null && session != null) {
await session.DisposeAsync().ConfigureAwait(false);
throw;
}
finally {
if (connection != null)
await connection.DisposeAsync().ConfigureAwait(false);
}
}
}
}
}

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

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.CodeAnalysis.Completion;
namespace MirrorSharp.Internal {
public struct TypeCharResult {
public TypeCharResult([CanBeNull] CompletionList completions) {
Completions = completions;
}
[CanBeNull] public CompletionList Completions { get; }
}
}

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

@ -0,0 +1,101 @@
using System;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Text;
namespace MirrorSharp.Internal {
public class WorkSession : IWorkSession {
private static readonly MefHostServices HostServices = MefHostServices.Create(MefHostServices.DefaultAssemblies.AddRange(new[] {
Assembly.Load(new AssemblyName("Microsoft.CodeAnalysis.Features")),
Assembly.Load(new AssemblyName("Microsoft.CodeAnalysis.CSharp.Features"))
}));
private readonly AdhocWorkspace _workspace;
private readonly TextChange[] _oneTextChange = new TextChange[1];
private Document _document;
private SourceText _text;
private int _cursorPosition;
private readonly CompletionService _completionService;
//private readonly Task _compilationLoopTask;
private readonly CancellationTokenSource _disposing;
public WorkSession() {
_disposing = new CancellationTokenSource();
//_compilationLoopTask = Task.Run(CompilationLoop);
_workspace = new AdhocWorkspace(HostServices);
var project = _workspace.AddProject("_", "C#");
_text = SourceText.From("");
_document = project.AddDocument("_", _text);
_completionService = CompletionService.GetService(_document);
if (_completionService == null)
throw new Exception("Failed to retrieve the completion service.");
}
public void ReplaceText(int start, int length, string newText, int cursorPositionAfter) {
_oneTextChange[0] = new TextChange(new TextSpan(start, length), newText);
_text = _text.WithChanges(_oneTextChange);
_document = _document.WithText(_text);
_cursorPosition = cursorPositionAfter;
}
public void ReplaceAllText(string newText) {
_text = SourceText.From(newText);
_document = _document.WithText(_text);
_cursorPosition = 0;
}
public void MoveCursor(int cursorPosition) {
_cursorPosition = cursorPosition;
}
public async Task<TypeCharResult> TypeCharAsync(char @char) {
ReplaceText(_cursorPosition, 1, FastConvert.CharToString(@char), _cursorPosition + 1);
var completions = await _completionService.GetCompletionsAsync(_document, _cursorPosition).ConfigureAwait(false);
return new TypeCharResult(completions);
}
/*private async Task CompilationLoop() {
var lastText = _text;
var lastCompilation = CSharpCompilation.Create("_", new[] {_document.GetSyntaxTreeAsync() });
while (!_disposing.IsCancellationRequested) {
if (_text != lastText) {
_completionService.ShouldTriggerCompletion()
var compilation = lastCompilation.ReplaceSyntaxTree(lastTree, tree);
_callback(compilation.GetDiagnostics().Select(d => d.GetMessage()).ToArray());
lastText = _text;
lastCompilation = compilation;
}
try {
await Task.Delay(500, _disposing.Token).ConfigureAwait(false);
}
catch (OperationCanceledException) {}
}
/ *return _compilation.GetDiagnostics().Select(d => new {
Message = d.GetMessage()
});* /
}*/
public async Task DisposeAsync() {
using (_disposing) {
_disposing.Cancel();
//await _compilationLoopTask.ConfigureAwait(false);
_workspace.Dispose();
}
}
}
}

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

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>c130c962-d17f-4741-b2b4-d74263bf380a</ProjectGuid>
<RootNamespace>MirrorSharp</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

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

@ -0,0 +1,19 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("MirrorSharp")]
[assembly: AssemblyTrademark("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("c130c962-d17f-4741-b2b4-d74263bf380a")]

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

@ -0,0 +1,20 @@
{
"version": "0.9.0-*",
"dependencies": {
"JetBrains.Annotations": "10.1.5",
"Microsoft.AspNetCore.Http.Abstractions": "1.0.0",
"Microsoft.CodeAnalysis.Common": "1.3.2",
"Microsoft.CodeAnalysis.CSharp": "1.3.2",
"Microsoft.CodeAnalysis.Workspaces.Common": "1.3.2",
"Microsoft.CodeAnalysis.CSharp.Features": "1.3.2",
"Newtonsoft.Json": "9.0.1",
"NETStandard.Library": "1.6.0"
},
"frameworks": {
"netstandard1.6": {
"imports": [ "dnxcore50", "portable-net45+win8+wp8+wpa81", "portable-net45+win8" ]
}
}
}