[gh-125] Implemented AST-hover for Roslyn.

This commit is contained in:
Andrey Shchekin 2017-06-20 21:59:21 +12:00
Родитель e7375fcd82
Коммит 6a4467b60c
13 изменённых файлов: 398 добавлений и 78 удалений

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

@ -3,6 +3,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using MirrorSharp.Advanced;
using SharpLab.Server.Decompilation.Internal;
@ -26,6 +27,7 @@ namespace SharpLab.Server.Decompilation.AstOnly {
var parentPropertyName = specialParentPropertyName ?? RoslynSyntaxHelper.GetParentPropertyName(node);
if (parentPropertyName != null)
writer.WriteProperty("property", parentPropertyName);
SerializeSpanProperty(node.FullSpan, writer);
writer.WritePropertyStartArray("children");
foreach (var child in node.ChildNodesAndTokens()) {
if (child.IsNode) {
@ -47,12 +49,18 @@ namespace SharpLab.Server.Decompilation.AstOnly {
if (parentPropertyName != null)
writer.WriteProperty("property", parentPropertyName);
SerializeSpanProperty(token.FullSpan, writer);
if (token.HasLeadingTrivia || token.HasTrailingTrivia) {
writer.WritePropertyStartArray("children");
foreach (var trivia in token.LeadingTrivia) {
SerializeTrivia(trivia, writer);
}
writer.WriteValue(token.ToString());
writer.WriteStartObject();
writer.WriteProperty("type", "value");
writer.WriteProperty("value", token.ValueText);
SerializeSpanProperty(token.Span, writer);
writer.WriteEndObject();
foreach (var trivia in token.TrailingTrivia) {
SerializeTrivia(trivia, writer);
}
@ -68,6 +76,7 @@ namespace SharpLab.Server.Decompilation.AstOnly {
writer.WriteStartObject();
writer.WriteProperty("type", "trivia");
writer.WriteProperty("kind", RoslynSyntaxHelper.GetKindName(trivia.RawKind));
SerializeSpanProperty(trivia.FullSpan, writer);
if (trivia.HasStructure) {
writer.WritePropertyStartArray("children");
SerializeNode(trivia.GetStructure(), writer, "Structure");
@ -79,6 +88,15 @@ namespace SharpLab.Server.Decompilation.AstOnly {
writer.WriteEndObject();
}
private void SerializeSpanProperty(TextSpan span, IFastJsonWriter writer) {
writer.WritePropertyName("range");
using (var stringWriter = writer.OpenString()) {
stringWriter.Write(span.Start);
stringWriter.Write(":");
stringWriter.Write(span.End);
}
}
public IReadOnlyCollection<string> SupportedLanguageNames { get; } = new[] {
LanguageNames.CSharp,
LanguageNames.VisualBasic

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

@ -20,7 +20,7 @@
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="1.2.1" />
<PackageReference Include="Microsoft.Owin.Cors" Version="3.0.1" />
<PackageReference Include="Microsoft.Owin.Host.SystemWeb" Version="3.0.1" />
<PackageReference Include="MirrorSharp.Common" Version="0.9.0-pre-20170528" />
<PackageReference Include="MirrorSharp.Common" Version="0.9.0-pre-20170620" />
<PackageReference Include="MirrorSharp.FSharp" Version="0.9.0-pre-20170523" />
<PackageReference Include="MirrorSharp.Owin" Version="0.9.0-pre-20170523" />
<PackageReference Include="MirrorSharp.VisualBasic" Version="0.9.0-pre-20170523" />

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

@ -7,19 +7,27 @@
{
"type": "node",
"kind": "CompilationUnit",
"range": "0:19",
"children": [
{
"type": "node",
"kind": "ClassDeclaration",
"range": "0:19",
"children": [
{
"type": "token",
"kind": "PublicKeyword",
"range": "0:7",
"children": [
"public",
{
"type": "value",
"value": "public",
"range": "0:6"
},
{
"type": "trivia",
"kind": "WhitespaceTrivia",
"range": "6:7",
"value": " "
}
]
@ -28,11 +36,17 @@
"type": "token",
"kind": "ClassKeyword",
"property": "Keyword",
"range": "7:13",
"children": [
"class",
{
"type": "value",
"value": "class",
"range": "7:12"
},
{
"type": "trivia",
"kind": "WhitespaceTrivia",
"range": "12:13",
"value": " "
}
]
@ -41,11 +55,17 @@
"type": "token",
"kind": "IdentifierToken",
"property": "Identifier",
"range": "13:15",
"children": [
"C",
{
"type": "value",
"value": "C",
"range": "13:14"
},
{
"type": "trivia",
"kind": "WhitespaceTrivia",
"range": "14:15",
"value": " "
}
]
@ -54,11 +74,17 @@
"type": "token",
"kind": "OpenBraceToken",
"property": "OpenBraceToken",
"range": "15:18",
"children": [
"{",
{
"type": "value",
"value": "{",
"range": "15:16"
},
{
"type": "trivia",
"kind": "EndOfLineTrivia",
"range": "16:18",
"value": "\r\n"
}
]
@ -67,6 +93,7 @@
"type": "token",
"kind": "CloseBraceToken",
"property": "CloseBraceToken",
"range": "18:19",
"value": "}"
}
]
@ -75,6 +102,7 @@
"type": "token",
"kind": "EndOfFileToken",
"property": "EndOfFileToken",
"range": "19:19",
"value": ""
}
]

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

@ -13,20 +13,28 @@ b";
{
"type": "node",
"kind": "CompilationUnit",
"range": "0:102",
"children": [
{
"type": "node",
"kind": "ClassDeclaration",
"range": "0:102",
"children": [
{
"type": "token",
"kind": "ClassKeyword",
"property": "Keyword",
"range": "0:6",
"children": [
"class",
{
"type": "value",
"value": "class",
"range": "0:5"
},
{
"type": "trivia",
"kind": "WhitespaceTrivia",
"range": "5:6",
"value": " "
}
]
@ -35,11 +43,17 @@ b";
"type": "token",
"kind": "IdentifierToken",
"property": "Identifier",
"range": "6:8",
"children": [
"C",
{
"type": "value",
"value": "C",
"range": "6:7"
},
{
"type": "trivia",
"kind": "WhitespaceTrivia",
"range": "7:8",
"value": " "
}
]
@ -48,11 +62,17 @@ b";
"type": "token",
"kind": "OpenBraceToken",
"property": "OpenBraceToken",
"range": "8:11",
"children": [
"{",
{
"type": "value",
"value": "{",
"range": "8:9"
},
{
"type": "trivia",
"kind": "EndOfLineTrivia",
"range": "9:11",
"value": "\r\n"
}
]
@ -60,31 +80,41 @@ b";
{
"type": "node",
"kind": "FieldDeclaration",
"range": "11:27",
"children": [
{
"type": "node",
"kind": "VariableDeclaration",
"property": "Declaration",
"range": "11:24",
"children": [
{
"type": "node",
"kind": "PredefinedType",
"property": "Type",
"range": "11:19",
"children": [
{
"type": "token",
"kind": "IntKeyword",
"property": "Keyword",
"range": "11:19",
"children": [
{
"type": "trivia",
"kind": "WhitespaceTrivia",
"range": "11:15",
"value": " "
},
"int",
{
"type": "value",
"value": "int",
"range": "15:18"
},
{
"type": "trivia",
"kind": "WhitespaceTrivia",
"range": "18:19",
"value": " "
}
]
@ -94,16 +124,23 @@ b";
{
"type": "node",
"kind": "VariableDeclarator",
"range": "19:24",
"children": [
{
"type": "token",
"kind": "IdentifierToken",
"property": "Identifier",
"range": "19:21",
"children": [
"i",
{
"type": "value",
"value": "i",
"range": "19:20"
},
{
"type": "trivia",
"kind": "WhitespaceTrivia",
"range": "20:21",
"value": " "
}
]
@ -112,16 +149,23 @@ b";
"type": "node",
"kind": "EqualsValueClause",
"property": "Initializer",
"range": "21:24",
"children": [
{
"type": "token",
"kind": "EqualsToken",
"property": "EqualsToken",
"range": "21:23",
"children": [
"=",
{
"type": "value",
"value": "=",
"range": "21:22"
},
{
"type": "trivia",
"kind": "WhitespaceTrivia",
"range": "22:23",
"value": " "
}
]
@ -130,11 +174,13 @@ b";
"type": "node",
"kind": "NumericLiteralExpression",
"property": "Value",
"range": "23:24",
"children": [
{
"type": "token",
"kind": "NumericLiteralToken",
"property": "Token",
"range": "23:24",
"value": "1"
}
]
@ -149,11 +195,17 @@ b";
"type": "token",
"kind": "SemicolonToken",
"property": "SemicolonToken",
"range": "24:27",
"children": [
";",
{
"type": "value",
"value": ";",
"range": "24:25"
},
{
"type": "trivia",
"kind": "EndOfLineTrivia",
"range": "25:27",
"value": "\r\n"
}
]
@ -163,31 +215,41 @@ b";
{
"type": "node",
"kind": "FieldDeclaration",
"range": "27:46",
"children": [
{
"type": "node",
"kind": "VariableDeclaration",
"property": "Declaration",
"range": "27:43",
"children": [
{
"type": "node",
"kind": "PredefinedType",
"property": "Type",
"range": "27:36",
"children": [
{
"type": "token",
"kind": "CharKeyword",
"property": "Keyword",
"range": "27:36",
"children": [
{
"type": "trivia",
"kind": "WhitespaceTrivia",
"range": "27:31",
"value": " "
},
"char",
{
"type": "value",
"value": "char",
"range": "31:35"
},
{
"type": "trivia",
"kind": "WhitespaceTrivia",
"range": "35:36",
"value": " "
}
]
@ -197,16 +259,23 @@ b";
{
"type": "node",
"kind": "VariableDeclarator",
"range": "36:43",
"children": [
{
"type": "token",
"kind": "IdentifierToken",
"property": "Identifier",
"range": "36:38",
"children": [
"c",
{
"type": "value",
"value": "c",
"range": "36:37"
},
{
"type": "trivia",
"kind": "WhitespaceTrivia",
"range": "37:38",
"value": " "
}
]
@ -215,16 +284,23 @@ b";
"type": "node",
"kind": "EqualsValueClause",
"property": "Initializer",
"range": "38:43",
"children": [
{
"type": "token",
"kind": "EqualsToken",
"property": "EqualsToken",
"range": "38:40",
"children": [
"=",
{
"type": "value",
"value": "=",
"range": "38:39"
},
{
"type": "trivia",
"kind": "WhitespaceTrivia",
"range": "39:40",
"value": " "
}
]
@ -233,11 +309,13 @@ b";
"type": "node",
"kind": "CharacterLiteralExpression",
"property": "Value",
"range": "40:43",
"children": [
{
"type": "token",
"kind": "CharacterLiteralToken",
"property": "Token",
"range": "40:43",
"value": "'c'"
}
]
@ -252,11 +330,17 @@ b";
"type": "token",
"kind": "SemicolonToken",
"property": "SemicolonToken",
"range": "43:46",
"children": [
";",
{
"type": "value",
"value": ";",
"range": "43:44"
},
{
"type": "trivia",
"kind": "EndOfLineTrivia",
"range": "44:46",
"value": "\r\n"
}
]
@ -266,36 +350,47 @@ b";
{
"type": "node",
"kind": "FieldDeclaration",
"range": "46:75",
"children": [
{
"type": "node",
"kind": "VariableDeclaration",
"property": "Declaration",
"range": "46:72",
"children": [
{
"type": "node",
"kind": "PredefinedType",
"property": "Type",
"range": "46:59",
"children": [
{
"type": "token",
"kind": "StringKeyword",
"property": "Keyword",
"range": "46:59",
"children": [
{
"type": "trivia",
"kind": "EndOfLineTrivia",
"range": "46:48",
"value": "\r\n"
},
{
"type": "trivia",
"kind": "WhitespaceTrivia",
"range": "48:52",
"value": " "
},
"string",
{
"type": "value",
"value": "string",
"range": "52:58"
},
{
"type": "trivia",
"kind": "WhitespaceTrivia",
"range": "58:59",
"value": " "
}
]
@ -305,16 +400,23 @@ b";
{
"type": "node",
"kind": "VariableDeclarator",
"range": "59:72",
"children": [
{
"type": "token",
"kind": "IdentifierToken",
"property": "Identifier",
"range": "59:62",
"children": [
"s1",
{
"type": "value",
"value": "s1",
"range": "59:61"
},
{
"type": "trivia",
"kind": "WhitespaceTrivia",
"range": "61:62",
"value": " "
}
]
@ -323,16 +425,23 @@ b";
"type": "node",
"kind": "EqualsValueClause",
"property": "Initializer",
"range": "62:72",
"children": [
{
"type": "token",
"kind": "EqualsToken",
"property": "EqualsToken",
"range": "62:64",
"children": [
"=",
{
"type": "value",
"value": "=",
"range": "62:63"
},
{
"type": "trivia",
"kind": "WhitespaceTrivia",
"range": "63:64",
"value": " "
}
]
@ -341,11 +450,13 @@ b";
"type": "node",
"kind": "StringLiteralExpression",
"property": "Value",
"range": "64:72",
"children": [
{
"type": "token",
"kind": "StringLiteralToken",
"property": "Token",
"range": "64:72",
"value": "\"a\\r\\nb\""
}
]
@ -360,11 +471,17 @@ b";
"type": "token",
"kind": "SemicolonToken",
"property": "SemicolonToken",
"range": "72:75",
"children": [
";",
{
"type": "value",
"value": ";",
"range": "72:73"
},
{
"type": "trivia",
"kind": "EndOfLineTrivia",
"range": "73:75",
"value": "\r\n"
}
]
@ -374,31 +491,41 @@ b";
{
"type": "node",
"kind": "FieldDeclaration",
"range": "75:101",
"children": [
{
"type": "node",
"kind": "VariableDeclaration",
"property": "Declaration",
"range": "75:98",
"children": [
{
"type": "node",
"kind": "PredefinedType",
"property": "Type",
"range": "75:86",
"children": [
{
"type": "token",
"kind": "StringKeyword",
"property": "Keyword",
"range": "75:86",
"children": [
{
"type": "trivia",
"kind": "WhitespaceTrivia",
"range": "75:79",
"value": " "
},
"string",
{
"type": "value",
"value": "string",
"range": "79:85"
},
{
"type": "trivia",
"kind": "WhitespaceTrivia",
"range": "85:86",
"value": " "
}
]
@ -408,16 +535,23 @@ b";
{
"type": "node",
"kind": "VariableDeclarator",
"range": "86:98",
"children": [
{
"type": "token",
"kind": "IdentifierToken",
"property": "Identifier",
"range": "86:89",
"children": [
"s2",
{
"type": "value",
"value": "s2",
"range": "86:88"
},
{
"type": "trivia",
"kind": "WhitespaceTrivia",
"range": "88:89",
"value": " "
}
]
@ -426,16 +560,23 @@ b";
"type": "node",
"kind": "EqualsValueClause",
"property": "Initializer",
"range": "89:98",
"children": [
{
"type": "token",
"kind": "EqualsToken",
"property": "EqualsToken",
"range": "89:91",
"children": [
"=",
{
"type": "value",
"value": "=",
"range": "89:90"
},
{
"type": "trivia",
"kind": "WhitespaceTrivia",
"range": "90:91",
"value": " "
}
]
@ -444,11 +585,13 @@ b";
"type": "node",
"kind": "StringLiteralExpression",
"property": "Value",
"range": "91:98",
"children": [
{
"type": "token",
"kind": "StringLiteralToken",
"property": "Token",
"range": "91:98",
"value": "@\"a\r\nb\""
}
]
@ -463,11 +606,17 @@ b";
"type": "token",
"kind": "SemicolonToken",
"property": "SemicolonToken",
"range": "98:101",
"children": [
";",
{
"type": "value",
"value": ";",
"range": "98:99"
},
{
"type": "trivia",
"kind": "EndOfLineTrivia",
"range": "99:101",
"value": "\r\n"
}
]
@ -478,6 +627,7 @@ b";
"type": "token",
"kind": "CloseBraceToken",
"property": "CloseBraceToken",
"range": "101:102",
"value": "}"
}
]
@ -486,6 +636,7 @@ b";
"type": "token",
"kind": "EndOfFileToken",
"property": "EndOfFileToken",
"range": "102:102",
"value": ""
}
]

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

@ -6,35 +6,46 @@
{
"type": "node",
"kind": "CompilationUnit",
"range": "0:27",
"children": [
{
"type": "token",
"kind": "EndOfFileToken",
"property": "EndOfFileToken",
"range": "0:27",
"children": [
{
"type": "trivia",
"kind": "SingleLineDocumentationCommentTrivia",
"range": "0:27",
"children": [
{
"type": "node",
"kind": "SingleLineDocumentationCommentTrivia",
"property": "Structure",
"range": "0:27",
"children": [
{
"type": "node",
"kind": "XmlText",
"range": "0:4",
"children": [
{
"type": "token",
"kind": "XmlTextLiteralToken",
"range": "0:4",
"children": [
{
"type": "trivia",
"kind": "DocumentationCommentExteriorTrivia",
"range": "0:3",
"value": "///"
},
" "
{
"type": "value",
"value": " ",
"range": "3:4"
}
]
}
]
@ -42,27 +53,32 @@
{
"type": "node",
"kind": "XmlElement",
"range": "4:27",
"children": [
{
"type": "node",
"kind": "XmlElementStartTag",
"property": "StartTag",
"range": "4:13",
"children": [
{
"type": "token",
"kind": "LessThanToken",
"property": "LessThanToken",
"range": "4:5",
"value": "<"
},
{
"type": "node",
"kind": "XmlName",
"property": "Name",
"range": "5:12",
"children": [
{
"type": "token",
"kind": "IdentifierToken",
"property": "LocalName",
"range": "5:12",
"value": "summary"
}
]
@ -71,6 +87,7 @@
"type": "token",
"kind": "GreaterThanToken",
"property": "GreaterThanToken",
"range": "12:13",
"value": ">"
}
]
@ -78,10 +95,12 @@
{
"type": "node",
"kind": "XmlText",
"range": "13:17",
"children": [
{
"type": "token",
"kind": "XmlTextLiteralToken",
"range": "13:17",
"value": "Test"
}
]
@ -90,22 +109,26 @@
"type": "node",
"kind": "XmlElementEndTag",
"property": "EndTag",
"range": "17:27",
"children": [
{
"type": "token",
"kind": "LessThanSlashToken",
"property": "LessThanSlashToken",
"range": "17:19",
"value": "</"
},
{
"type": "node",
"kind": "XmlName",
"property": "Name",
"range": "19:26",
"children": [
{
"type": "token",
"kind": "IdentifierToken",
"property": "LocalName",
"range": "19:26",
"value": "summary"
}
]
@ -114,6 +137,7 @@
"type": "token",
"kind": "GreaterThanToken",
"property": "GreaterThanToken",
"range": "26:27",
"value": ">"
}
]
@ -124,13 +148,18 @@
"type": "token",
"kind": "EndOfDocumentationCommentToken",
"property": "EndOfComment",
"range": "27:27",
"value": ""
}
]
}
]
},
""
{
"type": "value",
"value": "",
"range": "27:27"
}
]
}
]

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

@ -51,6 +51,7 @@
<app-mirrorsharp v-bind:initial-text="lastLoadedCode"
v-bind:service-url="serviceUrl"
v-bind:server-options="serverOptions"
v-bind:highlighted-range="highlightedCodeRange"
v-on:text-change="code = arguments[0]()"
v-on:slow-update-result="applyUpdateResult"
v-on:server-error="applyServerError"
@ -106,7 +107,8 @@
<app-ast-view class="ast"
v-if="lastResultOfType.ast"
v-show="result.type === 'ast'"
v-bind:roots="lastResultOfType.ast.value"></app-ast-view>
v-bind:roots="lastResultOfType.ast.value"
v-on:item-hover="applyAstHover"></app-ast-view>
</div>
</section>
@ -144,23 +146,18 @@
Built by Andrey Shchekin (<a href="http://twitter.com/ashmind">@ashmind</a>). See <a href="http://github.com/ashmind/SharpLab">SharpLab</a> on GitHub.
</footer>
<script type="text/x-template" id="app-ast-view">
<ol>
<li v-for="(item, index) in roots" class="collapsed" v-bind:class="{ leaf: !item.children }">
<span v-if="typeof item === 'string'" class="ast-item-wrap ast-item-value">
<span class="ast-item-type"></span>
{{renderValue(item, 'value')}}
</span>
<span v-else v-bind:class="'ast-item-wrap ast-item-' + item.type">
<button v-if="item.children"></button>
<span class="ast-item-type" v-bind:title="item.type"></span>
<span class="ast-item-property" v-if="item.property">{{item.property}}:</span>
<span v-if="item.value !== undefined" class="ast-inline-value">{{renderValue(item.value, item.type)}}</span>
<span class="ast-item-kind">{{item.kind}}</span>
</span>
<app-ast-view v-if="item.children" v-bind:roots="item.children"></app-ast-view>
</li>
</ol>
<script type="text/x-template" id="app-ast-view-item">
<span v-if="typeof item === 'string'" class="ast-item-wrap ast-item-value">
<span class="ast-item-type"></span>
{{renderValue(item, 'value')}}
</span>
<span v-else v-bind:class="'ast-item-wrap ast-item-' + item.type">
<button v-if="item.children"></button>
<span class="ast-item-type" v-bind:title="item.type"></span>
<span class="ast-item-property" v-if="item.property">{{item.property}}:</span>
<span v-if="item.value !== undefined" class="ast-inline-value">{{renderValue(item.value, item.type)}}</span>
<span class="ast-item-kind">{{item.kind}}</span>
</span>
</script>
<!-- build:js -->

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

@ -52,6 +52,16 @@ function getServiceUrl(branch) {
return `${httpRoot.replace(/^http/, 'ws')}/mirrorsharp`;
}
function applyAstHover(item) {
if (!item || !item.range) {
this.highlightedCodeRange = null;
return;
}
const [start, end] = item.range.split(':');
this.highlightedCodeRange = { start, end };
}
async function createAppAsync() {
const data = Object.assign({
languages,
@ -70,7 +80,9 @@ async function createAppAsync() {
errors: [],
warnings: []
},
lastResultOfType: { code: null, ast: null }
lastResultOfType: { code: null, ast: null },
highlightedCodeRange: null
});
await state.loadAsync(data);
data.lastLoadedCode = data.code;
@ -114,7 +126,7 @@ async function createAppAsync() {
return { name: 'default', color: '#4684ee' };
}
},
methods: { applyUpdateResult, applyServerError, applyConnectionChange }
methods: { applyUpdateResult, applyServerError, applyConnectionChange, applyAstHover }
};
}

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

@ -1,44 +1,71 @@
import Vue from 'vue';
import AstViewItem from './internal/app-ast-view-item.js';
Vue.component('app-ast-view', {
props: {
roots: Array
},
methods: {
renderValue: function(value, type) {
if (type === 'trivia')
return escapeTrivia(value);
return escapeCommon(value);
}
},
data: () => ({
expanded: []
}),
mounted: function() {
const getItem = li => this.allById[li.getAttribute('data-id')];
let hoverSupported = false;
let lastHoverByClick = null;
Vue.nextTick(() => {
this.$el.addEventListener('click', e => {
const li = findLI(e);
if (!li)
return;
handleOnLI(this.$el, 'click', li => {
li.classList.toggle('collapsed');
e.stopPropagation();
if (hoverSupported)
return;
if (lastHoverByClick)
lastHoverByClick.classList.remove('hover');
this.$emit('item-hover', getItem(li));
li.classList.add('hover');
lastHoverByClick = li;
});
handleOnLI(this.$el, 'mouseover', li => {
hoverSupported = true;
this.$emit('item-hover', getItem(li));
li.classList.add('hover');
});
handleOnLI(this.$el, 'mouseout', li => {
this.$emit('item-hover', null);
li.classList.remove('hover');
});
});
},
template: '#app-ast-view'
render: function(h) {
this.allById = {};
return h('div', [renderTree(h, this.roots, this.allById)]);
}
});
function escapeCommon(value) {
return value
.replace('\r', '\\r')
.replace('\n', '\\n')
.replace('\t', '\\t');
function renderTree(h, items, allById, parentId) {
return h('ol',
items.map((item, index) => renderLI(h, item, allById, (parentId != null) ? parentId + '.' + index : index))
);
}
function escapeTrivia(value) {
return escapeCommon(value)
.replace(/(^ +| +$)/g, (_,$1) => $1.length > 1 ? `<space:${$1.length}>` : '<space>');
function renderLI(h, item, allById, id) {
allById[id] = item;
return h('li',
{
class: { collapsed: true, leaf: !item.children },
attrs: { 'data-id': id }
},
[
h(AstViewItem, { props: { item } }),
item.children ? renderTree(h, item.children, allById, id) : null
]
);
}
function handleOnLI(root, event, action) {
root.addEventListener(event, e => {
const li = findLI(e);
if (!li)
return;
action(li);
});
}
function findLI(e) {

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

@ -4,9 +4,10 @@ import 'codemirror/mode/mllike/mllike';
Vue.component('app-mirrorsharp', {
props: {
initialText: String,
serverOptions: Object,
serviceUrl: String
initialText: String,
serverOptions: Object,
serviceUrl: String,
highlightedRange: Object
},
mounted: function() {
Vue.nextTick(() => {
@ -41,6 +42,21 @@ Vue.component('app-mirrorsharp', {
if (this.serverOptions)
instance.sendServerOptions(this.serverOptions);
});
let currentMarker = null;
this.$watch('highlightedRange', range => {
const cm = instance.getCodeMirror();
if (currentMarker) {
currentMarker.clear();
currentMarker = null;
}
if (!range)
return;
const from = cm.posFromIndex(range.start);
const to = cm.posFromIndex(range.end);
currentMarker = cm.markText(from, to, { className: 'highlighted' });
});
});
},
template: '<textarea></textarea>'

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

@ -0,0 +1,28 @@
export default {
props: {
item: {}
},
methods: {
renderValue
},
template: '#app-ast-view-item'
};
function renderValue(value, type) {
if (type === 'trivia')
return escapeTrivia(value);
return escapeCommon(value);
}
function escapeCommon(value) {
return value
.replace('\r', '\\r')
.replace('\n', '\\n')
.replace('\t', '\\t');
}
function escapeTrivia(value) {
return escapeCommon(value)
.replace(/(^ +| +$)/g, (_,$1) => $1.length > 1 ? `<space:${$1.length}>` : '<space>');
}

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

@ -7,9 +7,8 @@
bottom: 0;
right: 0;
overflow: auto;
display: flex;
padding: 0;
margin: 0;
margin-left: 4px;
margin-top: 2px;
@ -20,13 +19,23 @@
padding-left: 20px;
}
> ol {
padding: 0;
}
li {
cursor: pointer;
padding-top: 4px;
padding-top: 2px;
padding-bottom: 2px;
padding-right: 6px;
&:first-child {
padding-top: 2px;
}
&.hover {
background-color: @highlight-color;
}
}
button {

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

@ -34,4 +34,8 @@ textarea, .CodeMirror {
.cm-tag {
color: #bbb;
}
}
.highlighted {
background-color: @highlight-color;
}

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

@ -2,6 +2,7 @@
@error-color: #dc3912;
@warning-color: #ff9900;
@offline-color: #aaa;
@highlight-color: #efefef;
.code-text() {
font-family: Consolas, Menlo, Monaco, monospace;