fixed broken lints
This commit is contained in:
parent
91e6651bae
commit
58c04d96c2
9 changed files with 512 additions and 178 deletions
|
@ -1,85 +1,85 @@
|
||||||
{
|
{
|
||||||
"name": "angel-dart-vscode",
|
"name": "angel-dart-vscode",
|
||||||
"displayName": "Angel",
|
"displayName": "Angel",
|
||||||
"description": "Snippets and IDE support for the Angel server framework within VSCode.",
|
"description": "Snippets and IDE support for the Angel server framework within VSCode.",
|
||||||
"version": "0.0.1",
|
"version": "0.2.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/angel-dart/vscode"
|
"url": "https://github.com/angel-dart/vscode"
|
||||||
},
|
},
|
||||||
"icon": "media/logo.png",
|
"icon": "media/logo.png",
|
||||||
"publisher": "thosakwe0541",
|
"publisher": "thosakwe0541",
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "^1.28.0"
|
"vscode": "^1.28.0"
|
||||||
},
|
},
|
||||||
"categories": [
|
"categories": [
|
||||||
"Snippets"
|
"Snippets"
|
||||||
],
|
|
||||||
"keywords": [
|
|
||||||
"angel",
|
|
||||||
"angel-dart",
|
|
||||||
"dart",
|
|
||||||
"jael",
|
|
||||||
"template",
|
|
||||||
"templating",
|
|
||||||
"flutter",
|
|
||||||
"fuchsia"
|
|
||||||
],
|
|
||||||
"activationEvents": [
|
|
||||||
"onLanguage:jael"
|
|
||||||
],
|
|
||||||
"main": "./out/extension",
|
|
||||||
"contributes": {
|
|
||||||
"_commands": [
|
|
||||||
{
|
|
||||||
"command": "extension.sayHello",
|
|
||||||
"title": "Hello World"
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"languages": [
|
"keywords": [
|
||||||
{
|
"angel",
|
||||||
"id": "jael",
|
"angel-dart",
|
||||||
"aliases": [
|
"dart",
|
||||||
"Jael"
|
"jael",
|
||||||
|
"template",
|
||||||
|
"templating",
|
||||||
|
"flutter",
|
||||||
|
"fuchsia"
|
||||||
|
],
|
||||||
|
"activationEvents": [
|
||||||
|
"onLanguage:jael"
|
||||||
|
],
|
||||||
|
"main": "./out/extension",
|
||||||
|
"contributes": {
|
||||||
|
"_commands": [
|
||||||
|
{
|
||||||
|
"command": "extension.sayHello",
|
||||||
|
"title": "Hello World"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"extensions": [
|
"languages": [
|
||||||
".jael"
|
{
|
||||||
|
"id": "jael",
|
||||||
|
"aliases": [
|
||||||
|
"Jael"
|
||||||
|
],
|
||||||
|
"extensions": [
|
||||||
|
".jael"
|
||||||
|
],
|
||||||
|
"configuration": "./syntaxes/jael-language-configuration.json"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"configuration": "./syntaxes/jael-language-configuration.json"
|
"grammars": [
|
||||||
}
|
{
|
||||||
],
|
"language": "jael",
|
||||||
"grammars": [
|
"scopeName": "source.jael",
|
||||||
{
|
"path": "./syntaxes/jael.json"
|
||||||
"language": "jael",
|
}
|
||||||
"scopeName": "source.jael",
|
],
|
||||||
"path": "./syntaxes/jael.json"
|
"snippets": [
|
||||||
}
|
{
|
||||||
],
|
"language": "dart",
|
||||||
"snippets": [
|
"path": "./snippets/angel.json"
|
||||||
{
|
},
|
||||||
"language": "dart",
|
{
|
||||||
"path": "./snippets/angel.json"
|
"language": "jael",
|
||||||
},
|
"path": "./snippets/jael.json"
|
||||||
{
|
}
|
||||||
"language": "jael",
|
]
|
||||||
"path": "./snippets/jael.json"
|
},
|
||||||
}
|
"scripts": {
|
||||||
]
|
"vscode:prepublish": "npm run compile",
|
||||||
},
|
"compile": "tsc -p ./",
|
||||||
"scripts": {
|
"watch": "tsc -watch -p ./",
|
||||||
"vscode:prepublish": "npm run compile",
|
"postinstall": "node ./node_modules/vscode/bin/install",
|
||||||
"compile": "tsc -p ./",
|
"test": "npm run compile && node ./node_modules/vscode/bin/test"
|
||||||
"watch": "tsc -watch -p ./",
|
},
|
||||||
"postinstall": "node ./node_modules/vscode/bin/install",
|
"devDependencies": {
|
||||||
"test": "npm run compile && node ./node_modules/vscode/bin/test"
|
"@types/mocha": "^2.2.42",
|
||||||
},
|
"@types/node": "^7.0.43",
|
||||||
"devDependencies": {
|
"typescript": "^2.6.1",
|
||||||
"@types/mocha": "^2.2.42",
|
"vscode": "^1.1.6"
|
||||||
"@types/node": "^7.0.43",
|
},
|
||||||
"typescript": "^2.6.1",
|
"dependencies": {
|
||||||
"vscode": "^1.1.6"
|
"vscode-languageclient": "^5.1.1"
|
||||||
},
|
}
|
||||||
"dependencies": {
|
}
|
||||||
"vscode-languageclient": "^5.1.1"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -31,19 +31,40 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"begin": "<\\s*(script)[^>]*>",
|
"begin": "<\\s*(script)[^>]*>",
|
||||||
"end": "<\\s*/\\s*(script)[^>]*>",
|
"end": "(.*)<\\s*/\\s*(script)[^>]*>",
|
||||||
"beginCaptures": {
|
"beginCaptures": {
|
||||||
"1": {
|
"1": {
|
||||||
"name": "keyword.tag.jael"
|
"name": "keyword.tag.embedded.js.jael"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"endCaptures": {
|
"endCaptures": {
|
||||||
"1": {
|
"1": {
|
||||||
"name": "keyword.tag.jael"
|
"name": "source.js",
|
||||||
|
"contentName": "source.js"
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"name": "keyword.tag.embedded.js.jael"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"contentName": "source.js",
|
||||||
"patterns": [{ "include": "source.js" }]
|
"patterns": [{ "include": "source.js" }]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"begin": "<\\s*(style)[^>]*>",
|
||||||
|
"end": "<\\s*/\\s*(style)[^>]*>",
|
||||||
|
"beginCaptures": {
|
||||||
|
"1": {
|
||||||
|
"name": "keyword.tag.embedded.css.jael"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"endCaptures": {
|
||||||
|
"1": {
|
||||||
|
"name": "keyword.tag.embedded.css.jael"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contentName": "source.css",
|
||||||
|
"patterns": [{ "include": "source.css" }]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"match": "\\b(block|declare|for-each|extend|if|include|switch)\\b",
|
"match": "\\b(block|declare|for-each|extend|if|include|switch)\\b",
|
||||||
"name": "keyword.control.jael"
|
"name": "keyword.control.jael"
|
||||||
|
|
3
jael_language_server/analysis_options.yaml
Normal file
3
jael_language_server/analysis_options.yaml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
analyzer:
|
||||||
|
strong-mode:
|
||||||
|
implicit-casts: false
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:args/args.dart';
|
import 'package:args/args.dart';
|
||||||
import 'package:io/ansi.dart';
|
import 'package:io/ansi.dart';
|
||||||
|
@ -8,7 +9,8 @@ import 'package:jael_language_server/jael_language_server.dart';
|
||||||
main(List<String> args) async {
|
main(List<String> args) async {
|
||||||
var argParser = new ArgParser()
|
var argParser = new ArgParser()
|
||||||
..addFlag('help',
|
..addFlag('help',
|
||||||
abbr: 'h', negatable: false, help: 'Print this help information.');
|
abbr: 'h', negatable: false, help: 'Print this help information.')
|
||||||
|
..addOption('log-file', help: 'A path to which to write a log file.');
|
||||||
|
|
||||||
void printUsage() {
|
void printUsage() {
|
||||||
print('usage: jael_language_server [options...]\n\nOptions:');
|
print('usage: jael_language_server [options...]\n\nOptions:');
|
||||||
|
@ -24,20 +26,39 @@ main(List<String> args) async {
|
||||||
} else {
|
} else {
|
||||||
var jaelServer = new JaelLanguageServer();
|
var jaelServer = new JaelLanguageServer();
|
||||||
|
|
||||||
jaelServer.logger.onRecord.listen((rec) async {
|
if (argResults.wasParsed('log-file')) {
|
||||||
// TODO: Remove this
|
var f = new File(argResults['log-file'] as String);
|
||||||
var f = new File(
|
|
||||||
'/Users/thosakwe/Source/Angel/vscode/jael_language_server/.dart_tool/log.txt');
|
|
||||||
await f.create(recursive: true);
|
await f.create(recursive: true);
|
||||||
var sink = await f.openWrite(mode: FileMode.append);
|
|
||||||
sink.writeln(rec);
|
|
||||||
if (rec.error != null) sink.writeln(rec.error);
|
|
||||||
if (rec.stackTrace != null) sink.writeln(rec.stackTrace);
|
|
||||||
await sink.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
var stdio = new StdIOLanguageServer.start(jaelServer);
|
jaelServer.logger.onRecord.listen((rec) async {
|
||||||
await stdio.onDone;
|
var sink = await f.openWrite(mode: FileMode.append);
|
||||||
|
sink.writeln(rec);
|
||||||
|
if (rec.error != null) sink.writeln(rec.error);
|
||||||
|
if (rec.stackTrace != null) sink.writeln(rec.stackTrace);
|
||||||
|
await sink.close();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
jaelServer.logger.onRecord.listen((rec) async {
|
||||||
|
var sink = stderr;
|
||||||
|
sink.writeln(rec);
|
||||||
|
if (rec.error != null) sink.writeln(rec.error);
|
||||||
|
if (rec.stackTrace != null) sink.writeln(rec.stackTrace);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var spec = new ZoneSpecification(
|
||||||
|
handleUncaughtError: (self, parent, zone, error, stackTrace) {
|
||||||
|
jaelServer.logger.severe('Uncaught', error, stackTrace);
|
||||||
|
},
|
||||||
|
print: (self, parent, zone, line) {
|
||||||
|
jaelServer.logger.info(line);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
var zone = Zone.current.fork(specification: spec);
|
||||||
|
await zone.run(() async {
|
||||||
|
var stdio = new StdIOLanguageServer.start(jaelServer);
|
||||||
|
await stdio.onDone;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} on ArgParserException catch (e) {
|
} on ArgParserException catch (e) {
|
||||||
print('${red.wrap('error')}: ${e.message}\n');
|
print('${red.wrap('error')}: ${e.message}\n');
|
||||||
|
|
|
@ -17,32 +17,39 @@ class Analyzer extends Parser {
|
||||||
SymbolTable<JaelObject> get scope => _scope;
|
SymbolTable<JaelObject> get scope => _scope;
|
||||||
|
|
||||||
bool ensureAttributeIsPresent(Element element, String name) {
|
bool ensureAttributeIsPresent(Element element, String name) {
|
||||||
if (element.getAttribute(name) == null) {
|
if (element.getAttribute(name)?.value == null) {
|
||||||
errors.add(new JaelError(JaelErrorSeverity.error,
|
addError(new JaelError(JaelErrorSeverity.error,
|
||||||
'Missing required attribute `$name`.', element.span));
|
'Missing required attribute `$name`.', element.span));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void addError(JaelError e) {
|
||||||
|
errors.add(e);
|
||||||
|
logger.severe(e.message, e.span.highlight());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ensureAttributeIsConstantString(Element element, String name) {
|
||||||
|
var a = element.getAttribute(name);
|
||||||
|
if (a?.value is! StringLiteral || a?.value == null) {
|
||||||
|
var e = new JaelError(
|
||||||
|
JaelErrorSeverity.warning,
|
||||||
|
"`$name` attribute should be a constant string literal.",
|
||||||
|
a?.span ?? element.tagName.span);
|
||||||
|
addError(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Element parseElement() {
|
Element parseElement() {
|
||||||
try {
|
try {
|
||||||
_scope = _scope.createChild();
|
_scope = _scope.createChild();
|
||||||
var element = super.parseElement();
|
var element = super.parseElement();
|
||||||
if (element == null) {
|
if (element == null) return null;
|
||||||
// TODO: ???
|
|
||||||
if (next(TokenType.lt)) {
|
|
||||||
var tagName = parseIdentifier();
|
|
||||||
if (tagName != null) {
|
|
||||||
errors.add(
|
|
||||||
new JaelError(JaelErrorSeverity.error, "Hmm", tagName.span));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info('!!! ${element.tagName.name}');
|
|
||||||
|
|
||||||
// Check if any custom element exists.
|
// Check if any custom element exists.
|
||||||
_scope
|
_scope
|
||||||
|
@ -52,48 +59,75 @@ class Analyzer extends Parser {
|
||||||
?.add(new SymbolUsage(SymbolUsageType.read, element.span));
|
?.add(new SymbolUsage(SymbolUsageType.read, element.span));
|
||||||
|
|
||||||
// Validate attrs
|
// Validate attrs
|
||||||
// TODO: if, for-each
|
var forEach = element.getAttribute('for-each');
|
||||||
|
if (forEach != null) {
|
||||||
|
var asAttr = element.getAttribute('as');
|
||||||
|
if (asAttr != null) {
|
||||||
|
if (ensureAttributeIsConstantString(element, 'as')) {
|
||||||
|
var asName = asAttr.string.value;
|
||||||
|
_scope.create(asName,
|
||||||
|
value: new JaelVariable(asName, asAttr.span), constant: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (forEach.value != null) {
|
||||||
|
addError(new JaelError(JaelErrorSeverity.error,
|
||||||
|
'Missing value for `for-each` directive.', forEach.span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var iff = element.getAttribute('if');
|
||||||
|
if (iff != null) {
|
||||||
|
if (iff.value != null) {
|
||||||
|
addError(new JaelError(JaelErrorSeverity.error,
|
||||||
|
'Missing value for `iff` directive.', iff.span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Validate the tag itself
|
// Validate the tag itself
|
||||||
if (element is RegularElement) {
|
if (element is RegularElement) {
|
||||||
if (element.tagName.name == 'block') {
|
if (element.tagName.name == 'block') {
|
||||||
ensureAttributeIsPresent(element, 'name');
|
ensureAttributeIsConstantString(element, 'name');
|
||||||
logger.info('Found <block> at ${element.span.start.toolString}');
|
//logger.info('Found <block> at ${element.span.start.toolString}');
|
||||||
} else if (element.tagName.name == 'case') {
|
} else if (element.tagName.name == 'case') {
|
||||||
ensureAttributeIsPresent(element, 'value');
|
ensureAttributeIsPresent(element, 'value');
|
||||||
logger.info('Found <case> at ${element.span.start.toolString}');
|
//logger.info('Found <case> at ${element.span.start.toolString}');
|
||||||
|
} else if (element.tagName.name == 'declare') {
|
||||||
|
if (element.attributes.isEmpty) {
|
||||||
|
addError(new JaelError(
|
||||||
|
JaelErrorSeverity.warning,
|
||||||
|
'`declare` directive does not define any new symbols.',
|
||||||
|
element.tagName.span));
|
||||||
|
} else {
|
||||||
|
for (var attr in element.attributes) {
|
||||||
|
_scope.create(attr.name,
|
||||||
|
value: new JaelVariable(attr.name, attr.span));
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (element.tagName.name == 'element') {
|
} else if (element.tagName.name == 'element') {
|
||||||
if (ensureAttributeIsPresent(element, 'name')) {
|
if (ensureAttributeIsConstantString(element, 'name')) {
|
||||||
var nameCtx = element.getAttribute('name').value;
|
var nameCtx = element.getAttribute('name').value as StringLiteral;
|
||||||
|
var name = nameCtx.value;
|
||||||
if (nameCtx is! StringLiteral) {
|
//logger.info(
|
||||||
errors.add(new JaelError(
|
// 'Found custom element $name at ${element.span.start.toolString}');
|
||||||
JaelErrorSeverity.warning,
|
try {
|
||||||
"`name` attribute should be a constant string literal.",
|
var symbol = parentScope.create(name,
|
||||||
nameCtx.span));
|
value: new JaelCustomElement(name, element.tagName.span),
|
||||||
} else {
|
constant: true);
|
||||||
var name = (nameCtx as StringLiteral).value;
|
allDefinitions.add(symbol);
|
||||||
logger.info(
|
} on StateError catch (e) {
|
||||||
'Found custom element $name at ${element.span.start.toolString}');
|
addError(new JaelError(
|
||||||
try {
|
JaelErrorSeverity.error, e.message, element.tagName.span));
|
||||||
var symbol = parentScope.create(name,
|
|
||||||
value: new JaelCustomElement(name, element.tagName.span),
|
|
||||||
constant: true);
|
|
||||||
allDefinitions.add(symbol);
|
|
||||||
} on StateError catch (e) {
|
|
||||||
errors.add(new JaelError(
|
|
||||||
JaelErrorSeverity.error, e.message, element.tagName.span));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (element.tagName.name == 'extend') {
|
} else if (element.tagName.name == 'extend') {
|
||||||
ensureAttributeIsPresent(element, 'src');
|
ensureAttributeIsConstantString(element, 'src');
|
||||||
logger.info('Found <extend> at ${element.span.start.toolString}');
|
//logger.info('Found <extend> at ${element.span.start.toolString}');
|
||||||
}
|
}
|
||||||
} else if (element is SelfClosingElement) {
|
} else if (element is SelfClosingElement) {
|
||||||
if (element.tagName.name == 'include') {
|
if (element.tagName.name == 'include') {
|
||||||
logger.info('Found <include> at ${element.span.start.toolString}');
|
//logger.info('Found <include> at ${element.span.start.toolString}');
|
||||||
ensureAttributeIsPresent(element, 'src');
|
ensureAttributeIsConstantString(element, 'src');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,4 +137,17 @@ class Analyzer extends Parser {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Expression parseExpression(int precedence) {
|
||||||
|
var expr = super.parseExpression(precedence);
|
||||||
|
if (expr == null) return null;
|
||||||
|
|
||||||
|
if (expr is Identifier) {
|
||||||
|
var ref = _scope.resolve(expr.name);
|
||||||
|
ref?.value?.usages?.add(new SymbolUsage(SymbolUsageType.read, expr.span));
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
115
jael_language_server/lib/src/formatter.dart
Normal file
115
jael_language_server/lib/src/formatter.dart
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
import 'package:jael/jael.dart';
|
||||||
|
|
||||||
|
class JaelFormatter {
|
||||||
|
final num tabSize;
|
||||||
|
final bool insertSpaces;
|
||||||
|
var _buffer = new StringBuffer();
|
||||||
|
int _level = 0;
|
||||||
|
String _spaces;
|
||||||
|
|
||||||
|
static String _spaceString(int tabSize) {
|
||||||
|
var b = new StringBuffer();
|
||||||
|
for (int i = 0; i < tabSize; i++) {
|
||||||
|
b.write(' ');
|
||||||
|
}
|
||||||
|
return b.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
JaelFormatter(this.tabSize, this.insertSpaces) {
|
||||||
|
_spaces = insertSpaces ? _spaceString(tabSize.toInt()) : '\t';
|
||||||
|
}
|
||||||
|
|
||||||
|
void _indent() {
|
||||||
|
_level++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _outdent() {
|
||||||
|
if (_level > 0) _level--;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _applySpacing() {
|
||||||
|
for (int i = 0; i < _level; i++) _buffer.write(_spaces);
|
||||||
|
}
|
||||||
|
|
||||||
|
String apply(Document document) {
|
||||||
|
if (document?.doctype != null) {
|
||||||
|
_buffer.write('<!doctype');
|
||||||
|
|
||||||
|
if (document.doctype.html != null) _buffer.write(' html');
|
||||||
|
if (document.doctype.public != null) _buffer.write(' public');
|
||||||
|
|
||||||
|
if (document.doctype.url != null) {
|
||||||
|
_buffer.write('${document.doctype.url}');
|
||||||
|
}
|
||||||
|
|
||||||
|
_buffer.writeln();
|
||||||
|
}
|
||||||
|
|
||||||
|
_formatChild(document?.root);
|
||||||
|
|
||||||
|
return _buffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _formatChild(ElementChild child) {
|
||||||
|
if (child == null) return;
|
||||||
|
_applySpacing();
|
||||||
|
if (child is Text)
|
||||||
|
_buffer.write(child.text.span.text);
|
||||||
|
else if (child is TextNode)
|
||||||
|
_buffer.write(child.text.span.text);
|
||||||
|
else if (child is Element) _formatElement(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _formatElement(Element element) {
|
||||||
|
_applySpacing();
|
||||||
|
_buffer.write('<${element.tagName.name}');
|
||||||
|
|
||||||
|
for (var attr in element.attributes) {
|
||||||
|
_buffer.write(' ${attr.name}');
|
||||||
|
|
||||||
|
if (attr.value != null) {
|
||||||
|
if (attr.value is Identifier) {
|
||||||
|
var id = attr.value as Identifier;
|
||||||
|
if (id.name == 'true') {
|
||||||
|
_buffer.write(id.name);
|
||||||
|
} else if (id.name != 'false') {
|
||||||
|
if (attr.nequ != null) _buffer.write('!=');
|
||||||
|
if (attr.equals != null) _buffer.write('=');
|
||||||
|
_buffer.write(id.name);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (attr.nequ != null) _buffer.write('!=');
|
||||||
|
if (attr.equals != null) _buffer.write('=');
|
||||||
|
_buffer.write(attr.value.span.text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element is SelfClosingElement) {
|
||||||
|
_buffer.writeln('/>');
|
||||||
|
} else if (element is RegularElement) {
|
||||||
|
if (element.children.length == 1 &&
|
||||||
|
(element.children.first is Text ||
|
||||||
|
element.children.first is TextNode)) {
|
||||||
|
_buffer.write('>');
|
||||||
|
_buffer.write(element.children.first.span.text);
|
||||||
|
} else {
|
||||||
|
_buffer.writeln('>');
|
||||||
|
_indent();
|
||||||
|
element.children.forEach(_formatChild);
|
||||||
|
_outdent();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.children.isNotEmpty &&
|
||||||
|
(element.children.last is Text ||
|
||||||
|
element.children.last is TextNode)) {
|
||||||
|
_buffer.writeln();
|
||||||
|
}
|
||||||
|
|
||||||
|
_applySpacing();
|
||||||
|
_buffer.writeln('</${element.tagName.name}>');
|
||||||
|
} else {
|
||||||
|
throw new ArgumentError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,11 @@ class JaelCustomElement extends JaelObject {
|
||||||
JaelCustomElement(this.name, FileSpan span) : super(span);
|
JaelCustomElement(this.name, FileSpan span) : super(span);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class JaelVariable extends JaelObject {
|
||||||
|
final String name;
|
||||||
|
JaelVariable(this.name, FileSpan span) : super(span);
|
||||||
|
}
|
||||||
|
|
||||||
class SymbolUsage {
|
class SymbolUsage {
|
||||||
final SymbolUsageType type;
|
final SymbolUsageType type;
|
||||||
final FileSpan span;
|
final FileSpan span;
|
||||||
|
|
|
@ -5,19 +5,22 @@ import 'package:file/file.dart';
|
||||||
import 'package:file/local.dart';
|
import 'package:file/local.dart';
|
||||||
import 'package:file/memory.dart';
|
import 'package:file/memory.dart';
|
||||||
import 'package:jael/jael.dart';
|
import 'package:jael/jael.dart';
|
||||||
|
import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc_2;
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
import 'package:source_span/source_span.dart';
|
import 'package:source_span/source_span.dart';
|
||||||
|
import 'package:string_scanner/string_scanner.dart';
|
||||||
import 'package:symbol_table/symbol_table.dart';
|
import 'package:symbol_table/symbol_table.dart';
|
||||||
import 'analyzer.dart';
|
import 'analyzer.dart';
|
||||||
|
import 'formatter.dart';
|
||||||
import 'object.dart';
|
import 'object.dart';
|
||||||
|
|
||||||
class JaelLanguageServer extends LanguageServer {
|
class JaelLanguageServer extends LanguageServer {
|
||||||
var _diagnostics = new StreamController<Diagnostics>(sync: true);
|
var _diagnostics = new StreamController<Diagnostics>();
|
||||||
var _done = new Completer();
|
var _done = new Completer();
|
||||||
var _memFs = new MemoryFileSystem();
|
var _memFs = new MemoryFileSystem();
|
||||||
var _localFs = const LocalFileSystem();
|
var _localFs = const LocalFileSystem();
|
||||||
Directory _localRootDir;
|
Directory _localRootDir, _memRootDir;
|
||||||
var logger = new Logger('jael');
|
var logger = new Logger('jael');
|
||||||
Uri _rootUri;
|
Uri _rootUri;
|
||||||
var _workspaceEdits = new StreamController<ApplyWorkspaceEditParams>();
|
var _workspaceEdits = new StreamController<ApplyWorkspaceEditParams>();
|
||||||
|
@ -39,25 +42,40 @@ class JaelLanguageServer extends LanguageServer {
|
||||||
return super.shutdown();
|
return super.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void setupExtraMethods(json_rpc_2.Peer peer) {
|
||||||
|
peer.registerMethod('textDocument/formatting',
|
||||||
|
(json_rpc_2.Parameters params) async {
|
||||||
|
var documentId =
|
||||||
|
new TextDocumentIdentifier.fromJson(params['textDocument'].asMap);
|
||||||
|
var formattingOptions =
|
||||||
|
new FormattingOptions.fromJson(params['options'].asMap);
|
||||||
|
return await textDocumentFormatting(documentId, formattingOptions);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<ServerCapabilities> initialize(int clientPid, String rootUri,
|
Future<ServerCapabilities> initialize(int clientPid, String rootUri,
|
||||||
ClientCapabilities clientCapabilities, String trace) async {
|
ClientCapabilities clientCapabilities, String trace) async {
|
||||||
// Find our real root dir.
|
// Find our real root dir.
|
||||||
_localRootDir = _localFs.directory(_rootUri = Uri.parse(rootUri));
|
_localRootDir = _localFs.directory(_rootUri = Uri.parse(rootUri));
|
||||||
|
_memRootDir = _memFs.directory('/');
|
||||||
|
await _memRootDir.create(recursive: true);
|
||||||
|
_memFs.currentDirectory = _memRootDir;
|
||||||
|
|
||||||
// Copy all real files that end in *.jael (and *.jl for legacy) into the in-memory filesystem.
|
// Copy all real files that end in *.jael (and *.jl for legacy) into the in-memory filesystem.
|
||||||
await for (var entity in _localRootDir.list(recursive: true)) {
|
await for (var entity in _localRootDir.list(recursive: true)) {
|
||||||
if (entity is File && p.extension(entity.path) == '.jael') {
|
if (entity is File && p.extension(entity.path) == '.jael') {
|
||||||
var relativePath =
|
logger.info('HEY ${entity.path}');
|
||||||
p.relative(entity.absolute.path, from: _localRootDir.absolute.path);
|
var file = _memFs.file(entity.absolute.path);
|
||||||
var file = _memFs.file(relativePath);
|
|
||||||
await file.create(recursive: true);
|
await file.create(recursive: true);
|
||||||
await entity.openRead().pipe(file.openWrite(mode: FileMode.write));
|
await entity.openRead().pipe(file.openWrite(mode: FileMode.write));
|
||||||
logger.info('Found Jael file ${file.path}');
|
logger.info(
|
||||||
|
'Found Jael file ${file.path}; copied to ${file.absolute.path}');
|
||||||
|
|
||||||
// Analyze it
|
// Analyze it
|
||||||
var documentId = new TextDocumentIdentifier((b) {
|
var documentId = new TextDocumentIdentifier((b) {
|
||||||
b..uri = _rootUri.replace(path: relativePath).toString();
|
b..uri = _rootUri.replace(path: file.path).toString();
|
||||||
});
|
});
|
||||||
|
|
||||||
await analyzerForId(documentId);
|
await analyzerForId(documentId);
|
||||||
|
@ -66,7 +84,7 @@ class JaelLanguageServer extends LanguageServer {
|
||||||
|
|
||||||
return new ServerCapabilities((b) {
|
return new ServerCapabilities((b) {
|
||||||
b
|
b
|
||||||
..codeActionProvider = false
|
..codeActionProvider = false
|
||||||
..completionProvider = new CompletionOptions((b) {
|
..completionProvider = new CompletionOptions((b) {
|
||||||
b
|
b
|
||||||
..resolveProvider = true
|
..resolveProvider = true
|
||||||
|
@ -90,7 +108,7 @@ class JaelLanguageServer extends LanguageServer {
|
||||||
..textDocumentSync = new TextDocumentSyncOptions((b) {
|
..textDocumentSync = new TextDocumentSyncOptions((b) {
|
||||||
b
|
b
|
||||||
..openClose = true
|
..openClose = true
|
||||||
..change = TextDocumentSyncKind.incremental
|
..change = TextDocumentSyncKind.full
|
||||||
..save = new SaveOptions((b) {
|
..save = new SaveOptions((b) {
|
||||||
b..includeText = false;
|
b..includeText = false;
|
||||||
})
|
})
|
||||||
|
@ -103,8 +121,16 @@ class JaelLanguageServer extends LanguageServer {
|
||||||
|
|
||||||
Future<File> fileForId(TextDocumentIdentifier documentId) async {
|
Future<File> fileForId(TextDocumentIdentifier documentId) async {
|
||||||
var uri = Uri.parse(documentId.uri);
|
var uri = Uri.parse(documentId.uri);
|
||||||
var relativePath = p.relative(uri.path, from: _rootUri.path);
|
var relativePath = uri.path;
|
||||||
var file = _memFs.file(relativePath);
|
var file = _memFs.directory('/').childFile(relativePath);
|
||||||
|
|
||||||
|
/*
|
||||||
|
logger.info('Searching for $relativePath. All:\n');
|
||||||
|
|
||||||
|
await for (var entity in _memFs.directory('/').list(recursive: true)) {
|
||||||
|
if (entity is File) print(' * ${entity.absolute.path}');
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
if (!await file.exists()) {
|
if (!await file.exists()) {
|
||||||
await file.create(recursive: true);
|
await file.create(recursive: true);
|
||||||
|
@ -124,9 +150,6 @@ class JaelLanguageServer extends LanguageServer {
|
||||||
var scanner = await scannerForId(documentId);
|
var scanner = await scannerForId(documentId);
|
||||||
var analyzer = new Analyzer(scanner, logger)..errors.addAll(scanner.errors);
|
var analyzer = new Analyzer(scanner, logger)..errors.addAll(scanner.errors);
|
||||||
analyzer.parseDocument();
|
analyzer.parseDocument();
|
||||||
logger.info(
|
|
||||||
'Done ${documentId.uri} ${await (await fileForId(documentId)).readAsString()}');
|
|
||||||
logger.info(analyzer.errors);
|
|
||||||
emitDiagnostics(documentId.uri, analyzer.errors.map(toDiagnostic).toList());
|
emitDiagnostics(documentId.uri, analyzer.errors.map(toDiagnostic).toList());
|
||||||
return analyzer;
|
return analyzer;
|
||||||
}
|
}
|
||||||
|
@ -203,14 +226,20 @@ class JaelLanguageServer extends LanguageServer {
|
||||||
..newText = '<$name\$1>\n \$2\n</name>';
|
..newText = '<$name\$1>\n \$2\n</name>';
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
} else if (value is JaelVariable) {
|
||||||
|
return new CompletionItem((b) {
|
||||||
|
b
|
||||||
|
..kind = CompletionItemKind.variable
|
||||||
|
..label = symbol.name;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void emitDiagnostics(String uri, Iterable<Diagnostic> diagnostics) {
|
void emitDiagnostics(String uri, Iterable<Diagnostic> diagnostics) {
|
||||||
if (diagnostics.isEmpty) return;
|
|
||||||
_diagnostics.add(new Diagnostics((b) {
|
_diagnostics.add(new Diagnostics((b) {
|
||||||
|
logger.info('$uri => ${diagnostics.map((d) => d.message).toList()}');
|
||||||
b
|
b
|
||||||
..diagnostics = diagnostics.toList()
|
..diagnostics = diagnostics.toList()
|
||||||
..uri = uri.toString();
|
..uri = uri.toString();
|
||||||
|
@ -230,9 +259,9 @@ class JaelLanguageServer extends LanguageServer {
|
||||||
var file = await fileForId(id);
|
var file = await fileForId(id);
|
||||||
|
|
||||||
for (var change in changes) {
|
for (var change in changes) {
|
||||||
if (change.range != null) {
|
if (change.text != null) {
|
||||||
await file.writeAsString(change.text);
|
await file.writeAsString(change.text);
|
||||||
} else {
|
} else if (change.range != null) {
|
||||||
var contents = await file.readAsString();
|
var contents = await file.readAsString();
|
||||||
|
|
||||||
int findIndex(Position position) {
|
int findIndex(Position position) {
|
||||||
|
@ -254,6 +283,7 @@ class JaelLanguageServer extends LanguageServer {
|
||||||
contents = contents.replaceRange(start, end, change.text);
|
contents = contents.replaceRange(start, end, change.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.info('${file.path} => $contents');
|
||||||
await file.writeAsString(contents);
|
await file.writeAsString(contents);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -281,26 +311,48 @@ class JaelLanguageServer extends LanguageServer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<JaelObject> currentSymbol(
|
final RegExp _id =
|
||||||
|
new RegExp(r'(([A-Za-z][A-Za-z0-9_]*-)*([A-Za-z][A-Za-z0-9_]*))');
|
||||||
|
|
||||||
|
Future<String> currentName(
|
||||||
TextDocumentIdentifier documentId, Position position) async {
|
TextDocumentIdentifier documentId, Position position) async {
|
||||||
var analyzer = await analyzerForId(documentId);
|
// First, read the file.
|
||||||
var symbols = analyzer.allDefinitions; // analyzer.scope.allVariables;
|
var file = await fileForId(documentId);
|
||||||
logger.info('Current synmbols: ${symbols.map((v) => v.name)}');
|
var contents = await file.readAsString();
|
||||||
|
|
||||||
for (var s in symbols) {
|
// Next, find the current index.
|
||||||
var v = s.value;
|
var scanner = new SpanScanner(contents);
|
||||||
|
|
||||||
if (position.line == v.span.start.line &&
|
while (!scanner.isDone &&
|
||||||
position.character == v.span.start.column) {
|
(scanner.state.line != position.line ||
|
||||||
logger.info('Success ${s.name}');
|
scanner.state.column != position.character)) {
|
||||||
return v;
|
scanner.readChar();
|
||||||
} else {
|
|
||||||
logger.info(
|
|
||||||
'Nope ${s.name} (${v.span.start.toolString} vs ${position.line}:${position.character})');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
// Next, just read the name.
|
||||||
|
if (scanner.matches(_id)) {
|
||||||
|
var longest = scanner.lastSpan.text;
|
||||||
|
|
||||||
|
while (scanner.matches(_id) && scanner.position > 0 && !scanner.isDone) {
|
||||||
|
longest = scanner.lastSpan.text;
|
||||||
|
scanner.position--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return longest;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<JaelObject> currentSymbol(
|
||||||
|
TextDocumentIdentifier documentId, Position position) async {
|
||||||
|
var name = await currentName(documentId, position);
|
||||||
|
if (name == null) return null;
|
||||||
|
var analyzer = await analyzerForId(documentId);
|
||||||
|
var symbols = analyzer.allDefinitions ?? analyzer.scope.allVariables;
|
||||||
|
logger
|
||||||
|
.info('Current symbols, seeking $name: ${symbols.map((v) => v.name)}');
|
||||||
|
return analyzer.scope.resolve(name)?.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -337,9 +389,12 @@ class JaelLanguageServer extends LanguageServer {
|
||||||
var symbol = await currentSymbol(documentId, position);
|
var symbol = await currentSymbol(documentId, position);
|
||||||
if (symbol != null) {
|
if (symbol != null) {
|
||||||
return new Hover((b) {
|
return new Hover((b) {
|
||||||
b..range = toRange(symbol.span);
|
b
|
||||||
|
..contents = symbol.span.text
|
||||||
|
..range = toRange(symbol.span);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -377,7 +432,10 @@ class JaelLanguageServer extends LanguageServer {
|
||||||
return new TextEdit((b) {
|
return new TextEdit((b) {
|
||||||
b
|
b
|
||||||
..range = toRange(u.span)
|
..range = toRange(u.span)
|
||||||
..newText = newName;
|
..newText = (symbol is JaelCustomElement &&
|
||||||
|
u.type == SymbolUsageType.definition)
|
||||||
|
? '"$newName"'
|
||||||
|
: newName;
|
||||||
});
|
});
|
||||||
}).toList()
|
}).toList()
|
||||||
};
|
};
|
||||||
|
@ -409,11 +467,73 @@ class JaelLanguageServer extends LanguageServer {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<SymbolInformation>> workspaceSymbol(String query) async {
|
Future<List<SymbolInformation>> workspaceSymbol(String query) async {
|
||||||
// TODO: implement workspaceSymbol
|
var values = <JaelObject>[];
|
||||||
return [];
|
|
||||||
|
await for (var file in _memRootDir.list(recursive: true)) {
|
||||||
|
if (file is File) {
|
||||||
|
var id = new TextDocumentIdentifier((b) {
|
||||||
|
b..uri = file.uri.toString();
|
||||||
|
});
|
||||||
|
var analyzer = await analyzerForId(id);
|
||||||
|
values.addAll(analyzer.allDefinitions.map((v) => v.value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return values.map((o) {
|
||||||
|
return new SymbolInformation((b) {
|
||||||
|
b
|
||||||
|
..name = o.name
|
||||||
|
..location = toLocation(o.span.sourceUrl.toString(), o.span)
|
||||||
|
..containerName = p.basename(o.span.sourceUrl.path)
|
||||||
|
..kind = o is JaelCustomElement
|
||||||
|
? SymbolKind.classSymbol
|
||||||
|
: SymbolKind.variable;
|
||||||
|
});
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<TextEdit>> textDocumentFormatting(
|
||||||
|
TextDocumentIdentifier documentId,
|
||||||
|
FormattingOptions formattingOptions) async {
|
||||||
|
try {
|
||||||
|
var errors = <JaelError>[];
|
||||||
|
var file = await fileForId(documentId);
|
||||||
|
var contents = await file.readAsString();
|
||||||
|
var document =
|
||||||
|
parseDocument(contents, sourceUrl: file.uri, onError: errors.add);
|
||||||
|
if (errors.isNotEmpty) return null;
|
||||||
|
var formatter = new JaelFormatter(
|
||||||
|
formattingOptions.tabSize, formattingOptions.insertSpaces);
|
||||||
|
var formatted = formatter.apply(document);
|
||||||
|
logger.info('Original:${contents}\nFormatted:\n$formatted');
|
||||||
|
if (formatted.isNotEmpty) await file.writeAsString(formatted);
|
||||||
|
return [
|
||||||
|
new TextEdit((b) {
|
||||||
|
b
|
||||||
|
..newText = formatted
|
||||||
|
..range = document == null ? emptyRange() : toRange(document.span);
|
||||||
|
})
|
||||||
|
];
|
||||||
|
} catch (e, st) {
|
||||||
|
logger.severe('Formatter error', e, st);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class DiagnosticSeverity {
|
abstract class DiagnosticSeverity {
|
||||||
static const int error = 0, warning = 1, information = 2, hint = 3;
|
static const int error = 0, warning = 1, information = 2, hint = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class FormattingOptions {
|
||||||
|
final num tabSize;
|
||||||
|
|
||||||
|
final bool insertSpaces;
|
||||||
|
|
||||||
|
FormattingOptions(this.tabSize, this.insertSpaces);
|
||||||
|
|
||||||
|
factory FormattingOptions.fromJson(Map json) {
|
||||||
|
return new FormattingOptions(
|
||||||
|
json['tabSize'] as num, json['insertSpaces'] as bool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -12,9 +12,11 @@ dependencies:
|
||||||
io: ^0.3.2
|
io: ^0.3.2
|
||||||
jael: ^2.0.0
|
jael: ^2.0.0
|
||||||
jael_preprocessor: ^2.0.0
|
jael_preprocessor: ^2.0.0
|
||||||
|
json_rpc_2: ^2.0.0
|
||||||
logging: ^0.11.3
|
logging: ^0.11.3
|
||||||
path: ^1.0.0
|
path: ^1.0.0
|
||||||
symbol_table: ^2.0.0
|
|
||||||
source_span: ^1.0.0
|
source_span: ^1.0.0
|
||||||
|
string_scanner: ^1.0.0
|
||||||
|
symbol_table: ^2.0.0
|
||||||
executables:
|
executables:
|
||||||
jael_language_server: jael_language_server
|
jael_language_server: jael_language_server
|
Loading…
Reference in a new issue