Moved vscode to own repo
This commit is contained in:
parent
566598a384
commit
91cd22f280
32 changed files with 0 additions and 9152 deletions
4
packages/vscode/angel_vscode/.gitignore
vendored
4
packages/vscode/angel_vscode/.gitignore
vendored
|
@ -1,4 +0,0 @@
|
||||||
out
|
|
||||||
node_modules
|
|
||||||
.vscode-test/
|
|
||||||
*.vsix
|
|
36
packages/vscode/angel_vscode/.vscode/launch.json
vendored
36
packages/vscode/angel_vscode/.vscode/launch.json
vendored
|
@ -1,36 +0,0 @@
|
||||||
// A launch configuration that compiles the extension and then opens it inside a new window
|
|
||||||
// Use IntelliSense to learn about possible attributes.
|
|
||||||
// Hover to view descriptions of existing attributes.
|
|
||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
|
||||||
{
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"name": "Extension",
|
|
||||||
"type": "extensionHost",
|
|
||||||
"request": "launch",
|
|
||||||
"runtimeExecutable": "${execPath}",
|
|
||||||
"args": [
|
|
||||||
"--extensionDevelopmentPath=${workspaceFolder}"
|
|
||||||
],
|
|
||||||
"outFiles": [
|
|
||||||
"${workspaceFolder}/out/**/*.js"
|
|
||||||
],
|
|
||||||
"preLaunchTask": "npm: watch"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Extension Tests",
|
|
||||||
"type": "extensionHost",
|
|
||||||
"request": "launch",
|
|
||||||
"runtimeExecutable": "${execPath}",
|
|
||||||
"args": [
|
|
||||||
"--extensionDevelopmentPath=${workspaceFolder}",
|
|
||||||
"--extensionTestsPath=${workspaceFolder}/out/test"
|
|
||||||
],
|
|
||||||
"outFiles": [
|
|
||||||
"${workspaceFolder}/out/test/**/*.js"
|
|
||||||
],
|
|
||||||
"preLaunchTask": "npm: watch"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
// Place your settings in this file to overwrite default and user settings.
|
|
||||||
{
|
|
||||||
"files.exclude": {
|
|
||||||
"out": false // set this to true to hide the "out" folder with the compiled JS files
|
|
||||||
},
|
|
||||||
"search.exclude": {
|
|
||||||
"out": true // set this to false to include "out" folder in search results
|
|
||||||
}
|
|
||||||
}
|
|
20
packages/vscode/angel_vscode/.vscode/tasks.json
vendored
20
packages/vscode/angel_vscode/.vscode/tasks.json
vendored
|
@ -1,20 +0,0 @@
|
||||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
|
||||||
// for the documentation about the tasks.json format
|
|
||||||
{
|
|
||||||
"version": "2.0.0",
|
|
||||||
"tasks": [
|
|
||||||
{
|
|
||||||
"type": "npm",
|
|
||||||
"script": "watch",
|
|
||||||
"problemMatcher": "$tsc-watch",
|
|
||||||
"isBackground": true,
|
|
||||||
"presentation": {
|
|
||||||
"reveal": "never"
|
|
||||||
},
|
|
||||||
"group": {
|
|
||||||
"kind": "build",
|
|
||||||
"isDefault": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
.vscode/**
|
|
||||||
.vscode-test/**
|
|
||||||
out/test/**
|
|
||||||
out/**/*.map
|
|
||||||
src/**
|
|
||||||
.gitignore
|
|
||||||
tsconfig.json
|
|
||||||
vsc-extension-quickstart.md
|
|
||||||
tslint.json
|
|
|
@ -1,7 +0,0 @@
|
||||||
# Change Log
|
|
||||||
All notable changes to the "angel-dart-vscode" extension will be documented in this file.
|
|
||||||
|
|
||||||
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
|
|
||||||
|
|
||||||
## [Unreleased]
|
|
||||||
- Initial release
|
|
|
@ -1,13 +0,0 @@
|
||||||
# Angel Framework support for VSCode
|
|
||||||
![The Angel Framework](https://angel-dart.github.io/assets/images/logo.png)
|
|
||||||
|
|
||||||
This extension provides IDE support within VSCode for the
|
|
||||||
[Angel](https://angel-dart.github.io)
|
|
||||||
Web server framework. The goal of this package is to improve
|
|
||||||
the overall development experience when working with Angel.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- Dart snippets for Angel framework code
|
|
||||||
- Syntax highlighting for the Jael templating engine
|
|
||||||
- **COMING SOON:** Language server support for Jael
|
|
Binary file not shown.
Before Width: | Height: | Size: 2.8 KiB |
2089
packages/vscode/angel_vscode/package-lock.json
generated
2089
packages/vscode/angel_vscode/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,85 +0,0 @@
|
||||||
{
|
|
||||||
"name": "angel-dart-vscode",
|
|
||||||
"displayName": "Angel",
|
|
||||||
"description": "Snippets and IDE support for the Angel server framework within VSCode.",
|
|
||||||
"version": "0.2.0",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/angel-dart/vscode"
|
|
||||||
},
|
|
||||||
"icon": "media/logo.png",
|
|
||||||
"publisher": "thosakwe0541",
|
|
||||||
"engines": {
|
|
||||||
"vscode": "^1.28.0"
|
|
||||||
},
|
|
||||||
"categories": [
|
|
||||||
"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": [
|
|
||||||
{
|
|
||||||
"id": "jael",
|
|
||||||
"aliases": [
|
|
||||||
"Jael"
|
|
||||||
],
|
|
||||||
"extensions": [
|
|
||||||
".jael"
|
|
||||||
],
|
|
||||||
"configuration": "./syntaxes/jael-language-configuration.json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"grammars": [
|
|
||||||
{
|
|
||||||
"language": "jael",
|
|
||||||
"scopeName": "source.jael",
|
|
||||||
"path": "./syntaxes/jael.json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"snippets": [
|
|
||||||
{
|
|
||||||
"language": "dart",
|
|
||||||
"path": "./snippets/angel.json"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"language": "jael",
|
|
||||||
"path": "./snippets/jael.json"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"vscode:prepublish": "npm run compile",
|
|
||||||
"compile": "tsc -p ./",
|
|
||||||
"watch": "tsc -watch -p ./",
|
|
||||||
"postinstall": "node ./node_modules/vscode/bin/install",
|
|
||||||
"test": "npm run compile && node ./node_modules/vscode/bin/test"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/mocha": "^2.2.42",
|
|
||||||
"@types/node": "^7.0.43",
|
|
||||||
"typescript": "^2.6.1",
|
|
||||||
"vscode": "^1.1.6"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"vscode-languageclient": "^5.1.1"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,115 +0,0 @@
|
||||||
{
|
|
||||||
".source.dart": {
|
|
||||||
"controller": {
|
|
||||||
"prefix": "angel:controller",
|
|
||||||
"description": "Generate an Angel Controller.",
|
|
||||||
"body": [
|
|
||||||
"import 'package:angel_framework/angel_framework.dart';",
|
|
||||||
"",
|
|
||||||
"@Expose('/$1')",
|
|
||||||
"class $1Controller extends Controller {",
|
|
||||||
" @Expose('/')",
|
|
||||||
" String hello() {",
|
|
||||||
" return 'Hello, world';",
|
|
||||||
" }",
|
|
||||||
" $2",
|
|
||||||
"}"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"websocket_controller": {
|
|
||||||
"prefix": "angel:websocket_controller",
|
|
||||||
"description": "Generate an Angel WebSocketController.",
|
|
||||||
"body": [
|
|
||||||
"import 'package:angel_websocket/server.dart';",
|
|
||||||
"",
|
|
||||||
"class $1Controller extends WebSocketController {",
|
|
||||||
" $1Controller(AngelWebSocket ws) : super(ws);",
|
|
||||||
"",
|
|
||||||
" @ExposeWs('ping')",
|
|
||||||
" void hello(WebSocketContext socket) {",
|
|
||||||
" socket.send('pong', {'message': 'Hello, world!'});",
|
|
||||||
" }",
|
|
||||||
" $2",
|
|
||||||
"}"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"model": {
|
|
||||||
"prefix": "angel:model",
|
|
||||||
"description": "Generate an Angel Model.",
|
|
||||||
"body": [
|
|
||||||
"import 'package:angel_model/angel_model.dart';",
|
|
||||||
"import 'package:angel_serialize/angel_serialize.dart';",
|
|
||||||
"part '$2.g.dart';",
|
|
||||||
"part '$2.serializer.g.dart';",
|
|
||||||
"",
|
|
||||||
"@serializable",
|
|
||||||
"abstract class _$1 extends Model {",
|
|
||||||
" $3",
|
|
||||||
"}"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"migration": {
|
|
||||||
"prefix": "angel:migration",
|
|
||||||
"description": "Generate an Angel ORM Migration.",
|
|
||||||
"body": [
|
|
||||||
"import 'package:angel_migration.dart/angel_migration.dart';",
|
|
||||||
"",
|
|
||||||
"class $1Migration extends Migration {",
|
|
||||||
" @override",
|
|
||||||
" void up(Schema schema) {",
|
|
||||||
" schema.create('$2', (table) {",
|
|
||||||
" table.serial('id').primaryKey();",
|
|
||||||
" table.date('created_at');",
|
|
||||||
" table.date('updated_at');",
|
|
||||||
" $3",
|
|
||||||
" });",
|
|
||||||
" }",
|
|
||||||
"",
|
|
||||||
" @override",
|
|
||||||
" void down(Schema schema) {",
|
|
||||||
" schema.drop('$2');",
|
|
||||||
" }",
|
|
||||||
"}"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"plugin": {
|
|
||||||
"prefix": "angel:plugin",
|
|
||||||
"description": "Generate an Angel plugin.",
|
|
||||||
"body": [
|
|
||||||
"import 'package:angel_framework/angel_framework.dart';",
|
|
||||||
"",
|
|
||||||
"AngelConfigurer $1() {",
|
|
||||||
" return (Angel app) async {",
|
|
||||||
" // Work some magic...",
|
|
||||||
" $2",
|
|
||||||
" };",
|
|
||||||
"}"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"test_driver": {
|
|
||||||
"prefix": "angel:test",
|
|
||||||
"description": "Generate an Angel test driver.",
|
|
||||||
"body": [
|
|
||||||
"import 'dart:io';",
|
|
||||||
"import 'package:$1/$1.dart' as $1;",
|
|
||||||
"import 'package:angel_framework/angel_framework.dart';",
|
|
||||||
"import 'package:angel_test/angel_test.dart';",
|
|
||||||
"import 'package:test/test.dart';",
|
|
||||||
"",
|
|
||||||
"main() async {",
|
|
||||||
" TestClient client;",
|
|
||||||
"",
|
|
||||||
" setUp(() async {",
|
|
||||||
" var app = new Angel();",
|
|
||||||
" await app.configure($1.configureServer);",
|
|
||||||
" client = await connectTo(app);",
|
|
||||||
" });",
|
|
||||||
"",
|
|
||||||
" tearDown(() => client.close());",
|
|
||||||
"",
|
|
||||||
" // Add your tests here...",
|
|
||||||
"}"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
{
|
|
||||||
".source.jael": {
|
|
||||||
"block": {
|
|
||||||
"prefix": "block",
|
|
||||||
"description": "Insert a named <block> tag.",
|
|
||||||
"body": ["<block name=\"$1\">", " $2", "</block>"]
|
|
||||||
},
|
|
||||||
"comment": {
|
|
||||||
"prefix": "comment",
|
|
||||||
"description": "Insert a comment.",
|
|
||||||
"body": ["<!-- $1 -->"]
|
|
||||||
},
|
|
||||||
"declare": {
|
|
||||||
"prefix": "declare",
|
|
||||||
"description": "Insert a <declare> tag.",
|
|
||||||
"body": ["<declare $1=$2>", " $3", "</declare>"]
|
|
||||||
},
|
|
||||||
"element": {
|
|
||||||
"prefix": "element",
|
|
||||||
"description": "Insert a custom <element> tag.",
|
|
||||||
"body": ["<element name=\"$1\">", " $2", "</element>"]
|
|
||||||
},
|
|
||||||
"extend": {
|
|
||||||
"prefix": "extend",
|
|
||||||
"description": "Insert an <extend> tag.",
|
|
||||||
"body": ["<extend src=\"$1\">", " $2", "</extend>"]
|
|
||||||
},
|
|
||||||
"for-each": {
|
|
||||||
"prefix": "for-each",
|
|
||||||
"description": "Insert a <for-each> tag.",
|
|
||||||
"body": ["<${1:div} for-each=$2 as=\"$3\">", " $4", "</$1>"]
|
|
||||||
},
|
|
||||||
"include": {
|
|
||||||
"prefix": "include",
|
|
||||||
"description": "Insert an <include /> tag.",
|
|
||||||
"body": ["<include src=\"$1\"/>"]
|
|
||||||
},
|
|
||||||
"switch": {
|
|
||||||
"prefix": "switch",
|
|
||||||
"description": "Insert a <switch> tag.",
|
|
||||||
"body": [
|
|
||||||
"<switch value=$1>",
|
|
||||||
" <case value=$2>",
|
|
||||||
" $3",
|
|
||||||
" </case>",
|
|
||||||
" <default>",
|
|
||||||
" $4",
|
|
||||||
" </default>",
|
|
||||||
"</switch>"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
"use strict";
|
|
||||||
|
|
||||||
import * as vscode from "vscode";
|
|
||||||
import { workspace } from "vscode";
|
|
||||||
import {
|
|
||||||
LanguageClient,
|
|
||||||
LanguageClientOptions,
|
|
||||||
ServerOptions,
|
|
||||||
TransportKind,
|
|
||||||
Executable
|
|
||||||
} from "vscode-languageclient";
|
|
||||||
|
|
||||||
export function activate(context: vscode.ExtensionContext) {
|
|
||||||
const runOpts: Executable = {
|
|
||||||
command: "pub",
|
|
||||||
args: ["global", "run", "jael_language_server"]
|
|
||||||
};
|
|
||||||
const serverOptions: ServerOptions = {
|
|
||||||
run: runOpts,
|
|
||||||
debug: runOpts,
|
|
||||||
transport: TransportKind.stdio
|
|
||||||
};
|
|
||||||
const clientOptions: LanguageClientOptions = {
|
|
||||||
documentSelector: [
|
|
||||||
{
|
|
||||||
scheme: "file",
|
|
||||||
language: "jael"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
synchronize: {
|
|
||||||
configurationSection: "jael",
|
|
||||||
fileEvents: workspace.createFileSystemWatcher("**/.jael")
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const lsp = new LanguageClient("jael", "Jael", serverOptions, clientOptions);
|
|
||||||
context.subscriptions.push(lsp.start());
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deactivate() {}
|
|
|
@ -1,28 +0,0 @@
|
||||||
{
|
|
||||||
"attribution-notice": {
|
|
||||||
"author-of-most-of-this-file": "Danny Tuppeny",
|
|
||||||
"source": "https://github.com/Dart-Code/Dart-Code/blob/master/syntaxes/dart.json"
|
|
||||||
},
|
|
||||||
"comments": {
|
|
||||||
"blockComment": ["<!--", "-->"]
|
|
||||||
},
|
|
||||||
"brackets": [["{", "}"], ["[", "]"], ["(", ")"], ["<", ">"]],
|
|
||||||
"autoClosingPairs": [
|
|
||||||
{ "open": "{", "close": "}" },
|
|
||||||
{ "open": "[", "close": "]" },
|
|
||||||
{ "open": "(", "close": ")" },
|
|
||||||
{ "open": "'", "close": "'", "notIn": ["string", "comment"] },
|
|
||||||
{ "open": "\"", "close": "\"", "notIn": ["string"] },
|
|
||||||
{ "open": "`", "close": "`", "notIn": ["string", "comment"] },
|
|
||||||
{ "open": "/**", "close": " */", "notIn": ["string"] }
|
|
||||||
],
|
|
||||||
"surroundingPairs": [
|
|
||||||
["{", "}"],
|
|
||||||
["[", "]"],
|
|
||||||
["(", ")"],
|
|
||||||
["<", ">"],
|
|
||||||
["'", "'"],
|
|
||||||
["\"", "\""],
|
|
||||||
["`", "`"]
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,172 +0,0 @@
|
||||||
{
|
|
||||||
"fileTypes": ["jael"],
|
|
||||||
"name": "Jael",
|
|
||||||
"scopeName": "source.jael",
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"include": "#expressions"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match": "\\b(DOCTYPE|doctype)\\b",
|
|
||||||
"name": "keyword.doctype.jael"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match": "[:,\\.]",
|
|
||||||
"name": "punctuation.jael"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"begin": "{{-?",
|
|
||||||
"end": "}}",
|
|
||||||
"beginCaptures": {
|
|
||||||
"0": {
|
|
||||||
"name": "keyword.operator.jael"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"endCaptures": {
|
|
||||||
"0": {
|
|
||||||
"name": "keyword.operator.jael"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"patterns": [{ "include": "#expressions" }, { "include": "#operators" }]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"begin": "<\\s*(script)[^>]*>",
|
|
||||||
"end": "(.*)<\\s*/\\s*(script)[^>]*>",
|
|
||||||
"beginCaptures": {
|
|
||||||
"1": {
|
|
||||||
"name": "keyword.tag.embedded.js.jael"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"endCaptures": {
|
|
||||||
"1": {
|
|
||||||
"name": "source.js",
|
|
||||||
"contentName": "source.js"
|
|
||||||
},
|
|
||||||
"2": {
|
|
||||||
"name": "keyword.tag.embedded.js.jael"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"contentName": "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",
|
|
||||||
"name": "keyword.control.jael"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match": "<\\s*/?\\s*([A-Za-z_][A-Za-z0-9_-]*)(\\s*[A-Za-z_][A-Za-z0-9_]*(?!=)\\s*)*\\b",
|
|
||||||
"captures": {
|
|
||||||
"1": {
|
|
||||||
"name": "keyword.tag.jael"
|
|
||||||
},
|
|
||||||
"2": {
|
|
||||||
"name": "storage.name.jael"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match": "@[A-Za-z_][A-Za-z0-9_]*",
|
|
||||||
"name": "storage.argument.jael"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match": "\\b(@?[A-Za-z_][A-Za-z0-9_]*)=",
|
|
||||||
"captures": {
|
|
||||||
"1": {
|
|
||||||
"name": "storage.name.jael"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"captures": {
|
|
||||||
"0": {
|
|
||||||
"name": "punctuation.definition.comment.jael"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"begin": "<!--",
|
|
||||||
"end": "-->",
|
|
||||||
"name": "comment.jael"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "#operators"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": "source.html"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"repository": {
|
|
||||||
"operators": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"match": "((!?=)|\\+|\\*|-|/|\\?|(\\?\\.)|%|\\[|\\])",
|
|
||||||
"name": "keyword.operator.jael"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"expressions": {
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"match": "\\b[<>]\\b",
|
|
||||||
"name": "keyword.operator.jael"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"begin": "'",
|
|
||||||
"end": "'",
|
|
||||||
"name": "string.quoted.single.jael",
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "constant.character.escape.jael",
|
|
||||||
"match": "\\\\[bfnrt']"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"begin": "\"",
|
|
||||||
"end": "\"",
|
|
||||||
"name": "string.quoted.double.jael",
|
|
||||||
"patterns": [
|
|
||||||
{
|
|
||||||
"name": "constant.character.escape.jael",
|
|
||||||
"match": "\\\\[bfnrt\"]"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match": "\\b(true|false|null)\\b",
|
|
||||||
"name": "constant.language.jael"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match": "\\b-?[0-9]+(\\.[0-9]+)?([Ee][0-9]+)?\\b",
|
|
||||||
"name": "constant.numeric.jael"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match": "\\b0x[A-Fa-f0-9]+\\b",
|
|
||||||
"name": "constant.numeric.jael"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match": "([A-Za-z_][A-Za-z0-9_]*)\\s*\\(",
|
|
||||||
"captures": {
|
|
||||||
"1": {
|
|
||||||
"name": "entity.name.function.jael"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"module": "commonjs",
|
|
||||||
"target": "es6",
|
|
||||||
"outDir": "out",
|
|
||||||
"lib": [
|
|
||||||
"es6"
|
|
||||||
],
|
|
||||||
"sourceMap": true,
|
|
||||||
"rootDir": "src",
|
|
||||||
/* Strict Type-Checking Option */
|
|
||||||
"strict": true, /* enable all strict type-checking options */
|
|
||||||
/* Additional Checks */
|
|
||||||
"noUnusedLocals": true /* Report errors on unused locals. */
|
|
||||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
|
||||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
|
||||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
|
||||||
},
|
|
||||||
"exclude": [
|
|
||||||
"node_modules",
|
|
||||||
".vscode-test"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
# Welcome to your VS Code Extension
|
|
||||||
|
|
||||||
## What's in the folder
|
|
||||||
* This folder contains all of the files necessary for your extension.
|
|
||||||
* `package.json` - this is the manifest file in which you declare your extension and command.
|
|
||||||
The sample plugin registers a command and defines its title and command name. With this information
|
|
||||||
VS Code can show the command in the command palette. It doesn’t yet need to load the plugin.
|
|
||||||
* `src/extension.ts` - this is the main file where you will provide the implementation of your command.
|
|
||||||
The file exports one function, `activate`, which is called the very first time your extension is
|
|
||||||
activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`.
|
|
||||||
We pass the function containing the implementation of the command as the second parameter to
|
|
||||||
`registerCommand`.
|
|
||||||
|
|
||||||
## Get up and running straight away
|
|
||||||
* Press `F5` to open a new window with your extension loaded.
|
|
||||||
* Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`.
|
|
||||||
* Set breakpoints in your code inside `src/extension.ts` to debug your extension.
|
|
||||||
* Find output from your extension in the debug console.
|
|
||||||
|
|
||||||
## Make changes
|
|
||||||
* You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`.
|
|
||||||
* You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes.
|
|
||||||
|
|
||||||
## Explore the API
|
|
||||||
* You can open the full set of our API when you open the file `node_modules/vscode/vscode.d.ts`.
|
|
||||||
|
|
||||||
## Run tests
|
|
||||||
* Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Launch Tests`.
|
|
||||||
* Press `F5` to run the tests in a new window with your extension loaded.
|
|
||||||
* See the output of the test result in the debug console.
|
|
||||||
* Make changes to `test/extension.test.ts` or create new test files inside the `test` folder.
|
|
||||||
* By convention, the test runner will only consider files matching the name pattern `**.test.ts`.
|
|
||||||
* You can create folders inside the `test` folder to structure your tests any way you want.
|
|
File diff suppressed because it is too large
Load diff
21
packages/vscode/jael_language_server/.gitignore
vendored
21
packages/vscode/jael_language_server/.gitignore
vendored
|
@ -1,21 +0,0 @@
|
||||||
# See https://www.dartlang.org/guides/libraries/private-files
|
|
||||||
|
|
||||||
# Files and directories created by pub
|
|
||||||
.dart_tool/
|
|
||||||
.packages
|
|
||||||
build/
|
|
||||||
# If you're building an application, you may want to check-in your pubspec.lock
|
|
||||||
pubspec.lock
|
|
||||||
|
|
||||||
# Directory created by dartdoc
|
|
||||||
# If you don't generate documentation locally you can remove this line.
|
|
||||||
doc/api/
|
|
||||||
|
|
||||||
# Avoid committing generated Javascript files:
|
|
||||||
*.dart.js
|
|
||||||
*.info.json # Produced by the --dump-info flag.
|
|
||||||
*.js # When generated by dart2js. Don't specify *.js if your
|
|
||||||
# project includes source files written in JavaScript.
|
|
||||||
*.js_
|
|
||||||
*.js.deps
|
|
||||||
*.js.map
|
|
|
@ -1,3 +0,0 @@
|
||||||
analyzer:
|
|
||||||
strong-mode:
|
|
||||||
implicit-casts: false
|
|
|
@ -1,69 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'package:args/args.dart';
|
|
||||||
import 'package:io/ansi.dart';
|
|
||||||
import 'package:io/io.dart';
|
|
||||||
//import 'package:dart_language_server/dart_language_server.dart';
|
|
||||||
import 'package:vs_jael_language_server/jael_language_server.dart';
|
|
||||||
import 'package:vs_jael_language_server/src/protocol/language_server/server.dart';
|
|
||||||
|
|
||||||
main(List<String> args) async {
|
|
||||||
var argParser = new ArgParser()
|
|
||||||
..addFlag('help',
|
|
||||||
abbr: 'h', negatable: false, help: 'Print this help information.')
|
|
||||||
..addOption('log-file', help: 'A path to which to write a log file.');
|
|
||||||
|
|
||||||
void printUsage() {
|
|
||||||
print('usage: jael_language_server [options...]\n\nOptions:');
|
|
||||||
print(argParser.usage);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
var argResults = argParser.parse(args);
|
|
||||||
|
|
||||||
if (argResults['help'] as bool) {
|
|
||||||
printUsage();
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
var jaelServer = new JaelLanguageServer();
|
|
||||||
|
|
||||||
if (argResults.wasParsed('log-file')) {
|
|
||||||
var f = new File(argResults['log-file'] as String);
|
|
||||||
await f.create(recursive: true);
|
|
||||||
|
|
||||||
jaelServer.logger.onRecord.listen((rec) async {
|
|
||||||
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 = StdIOLanguageServer.start(jaelServer);
|
|
||||||
await stdio.onDone;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} on ArgParserException catch (e) {
|
|
||||||
print('${red.wrap('error')}: ${e.message}\n');
|
|
||||||
printUsage();
|
|
||||||
exitCode = ExitCode.usage.code;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
export 'src/server.dart';
|
|
|
@ -1,153 +0,0 @@
|
||||||
import 'package:jael/jael.dart';
|
|
||||||
import 'package:logging/logging.dart';
|
|
||||||
import 'package:symbol_table/symbol_table.dart';
|
|
||||||
import 'object.dart';
|
|
||||||
|
|
||||||
class Analyzer extends Parser {
|
|
||||||
final Logger logger;
|
|
||||||
Analyzer(Scanner scanner, this.logger) : super(scanner);
|
|
||||||
|
|
||||||
final errors = <JaelError>[];
|
|
||||||
var _scope = new SymbolTable<JaelObject>();
|
|
||||||
var allDefinitions = <Variable<JaelObject>>[];
|
|
||||||
|
|
||||||
SymbolTable<JaelObject> get parentScope =>
|
|
||||||
_scope.isRoot ? _scope : _scope.parent;
|
|
||||||
|
|
||||||
SymbolTable<JaelObject> get scope => _scope;
|
|
||||||
|
|
||||||
bool ensureAttributeIsPresent(Element element, String name) {
|
|
||||||
if (element.getAttribute(name)?.value == null) {
|
|
||||||
addError(new JaelError(JaelErrorSeverity.error,
|
|
||||||
'Missing required attribute `$name`.', element.span));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
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
|
|
||||||
Element parseElement() {
|
|
||||||
try {
|
|
||||||
_scope = _scope.createChild();
|
|
||||||
var element = super.parseElement();
|
|
||||||
if (element == null) return null;
|
|
||||||
|
|
||||||
// Check if any custom element exists.
|
|
||||||
_scope
|
|
||||||
.resolve(element.tagName.name)
|
|
||||||
?.value
|
|
||||||
?.usages
|
|
||||||
?.add(new SymbolUsage(SymbolUsageType.read, element.span));
|
|
||||||
|
|
||||||
// Validate attrs
|
|
||||||
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
|
|
||||||
if (element is RegularElement) {
|
|
||||||
if (element.tagName.name == 'block') {
|
|
||||||
ensureAttributeIsConstantString(element, 'name');
|
|
||||||
//logger.info('Found <block> at ${element.span.start.toolString}');
|
|
||||||
} else if (element.tagName.name == 'case') {
|
|
||||||
ensureAttributeIsPresent(element, 'value');
|
|
||||||
//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') {
|
|
||||||
if (ensureAttributeIsConstantString(element, 'name')) {
|
|
||||||
var nameCtx = element.getAttribute('name').value as StringLiteral;
|
|
||||||
var name = nameCtx.value;
|
|
||||||
//logger.info(
|
|
||||||
// 'Found custom element $name at ${element.span.start.toolString}');
|
|
||||||
try {
|
|
||||||
var symbol = parentScope.create(name,
|
|
||||||
value: new JaelCustomElement(name, element.tagName.span),
|
|
||||||
constant: true);
|
|
||||||
allDefinitions.add(symbol);
|
|
||||||
} on StateError catch (e) {
|
|
||||||
addError(new JaelError(
|
|
||||||
JaelErrorSeverity.error, e.message, element.tagName.span));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (element.tagName.name == 'extend') {
|
|
||||||
ensureAttributeIsConstantString(element, 'src');
|
|
||||||
//logger.info('Found <extend> at ${element.span.start.toolString}');
|
|
||||||
}
|
|
||||||
} else if (element is SelfClosingElement) {
|
|
||||||
if (element.tagName.name == 'include') {
|
|
||||||
//logger.info('Found <include> at ${element.span.start.toolString}');
|
|
||||||
ensureAttributeIsConstantString(element, 'src');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return element;
|
|
||||||
} finally {
|
|
||||||
_scope = _scope.parent;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,115 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
import 'dart:collection';
|
|
||||||
|
|
||||||
import 'package:source_span/source_span.dart';
|
|
||||||
|
|
||||||
abstract class JaelObject {
|
|
||||||
final FileSpan span;
|
|
||||||
final usages = <SymbolUsage>[];
|
|
||||||
String get name;
|
|
||||||
|
|
||||||
JaelObject(this.span);
|
|
||||||
}
|
|
||||||
|
|
||||||
class JaelCustomElement extends JaelObject {
|
|
||||||
final String name;
|
|
||||||
final attributes = new SplayTreeSet<String>();
|
|
||||||
|
|
||||||
JaelCustomElement(this.name, FileSpan span) : super(span);
|
|
||||||
}
|
|
||||||
|
|
||||||
class JaelVariable extends JaelObject {
|
|
||||||
final String name;
|
|
||||||
JaelVariable(this.name, FileSpan span) : super(span);
|
|
||||||
}
|
|
||||||
|
|
||||||
class SymbolUsage {
|
|
||||||
final SymbolUsageType type;
|
|
||||||
final FileSpan span;
|
|
||||||
|
|
||||||
SymbolUsage(this.type, this.span);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum SymbolUsageType { definition, read }
|
|
|
@ -1,64 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:json_rpc_2/json_rpc_2.dart';
|
|
||||||
|
|
||||||
import 'messages.dart';
|
|
||||||
|
|
||||||
abstract class LanguageServer {
|
|
||||||
final _onDone = Completer<void>();
|
|
||||||
Future<void> get onDone => _onDone.future;
|
|
||||||
|
|
||||||
Future<void> shutdown() async {}
|
|
||||||
void exit() {
|
|
||||||
_onDone.complete();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<ServerCapabilities> initialize(int clientPid, String rootUri,
|
|
||||||
ClientCapabilities clientCapabilities, String trace) async =>
|
|
||||||
ServerCapabilities((b) => b);
|
|
||||||
void initialized() {}
|
|
||||||
void textDocumentDidOpen(TextDocumentItem document) {}
|
|
||||||
void textDocumentDidChange(VersionedTextDocumentIdentifier documentId,
|
|
||||||
List<TextDocumentContentChangeEvent> changes) {}
|
|
||||||
void textDocumentDidClose(TextDocumentIdentifier documentId) {}
|
|
||||||
Future<CompletionList> textDocumentCompletion(
|
|
||||||
TextDocumentIdentifier documentId, Position position) async =>
|
|
||||||
CompletionList((b) => b);
|
|
||||||
Future<Location> textDocumentDefinition(
|
|
||||||
TextDocumentIdentifier documentId, Position position) async =>
|
|
||||||
null;
|
|
||||||
Future<List<Location>> textDocumentReferences(
|
|
||||||
TextDocumentIdentifier documentId,
|
|
||||||
Position position,
|
|
||||||
ReferenceContext context) async =>
|
|
||||||
[];
|
|
||||||
Future<List<Location>> textDocumentImplementation(
|
|
||||||
TextDocumentIdentifier documentId, Position position) async =>
|
|
||||||
[];
|
|
||||||
Future<List<DocumentHighlight>> textDocumentHighlight(
|
|
||||||
TextDocumentIdentifier documentId, Position position) async =>
|
|
||||||
[];
|
|
||||||
Future<List<SymbolInformation>> textDocumentSymbols(
|
|
||||||
TextDocumentIdentifier documentId) async =>
|
|
||||||
[];
|
|
||||||
Future<List<SymbolInformation>> workspaceSymbol(String query) async => [];
|
|
||||||
Future<dynamic> textDocumentHover(
|
|
||||||
TextDocumentIdentifier documentId, Position position) async =>
|
|
||||||
null;
|
|
||||||
Future<List<dynamic /*Command|CodeAction*/ >> textDocumentCodeAction(
|
|
||||||
TextDocumentIdentifier documentId,
|
|
||||||
Range range,
|
|
||||||
CodeActionContext context) async =>
|
|
||||||
[];
|
|
||||||
Future<void> workspaceExecuteCommand(
|
|
||||||
String command, List<dynamic> arguments) async {}
|
|
||||||
Future<WorkspaceEdit> textDocumentRename(TextDocumentIdentifier documentId,
|
|
||||||
Position position, String newName) async =>
|
|
||||||
null;
|
|
||||||
Stream<Diagnostics> get diagnostics => Stream.empty();
|
|
||||||
Stream<ApplyWorkspaceEditParams> get workspaceEdits => Stream.empty();
|
|
||||||
Stream<ShowMessageParams> get showMessages => Stream.empty();
|
|
||||||
Stream<ShowMessageParams> get logMessages => Stream.empty();
|
|
||||||
|
|
||||||
void setupExtraMethods(Peer peer) {}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,321 +0,0 @@
|
||||||
TextDocumentItem:
|
|
||||||
uri: String
|
|
||||||
text: String
|
|
||||||
languageId: String
|
|
||||||
version: int
|
|
||||||
|
|
||||||
TextDocumentIdentifier:
|
|
||||||
uri: String
|
|
||||||
|
|
||||||
VersionedTextDocumentIdentifier:
|
|
||||||
uri: String
|
|
||||||
version: int
|
|
||||||
|
|
||||||
TextDocumentContentChangeEvent:
|
|
||||||
range: Range
|
|
||||||
rangeLength: int
|
|
||||||
text: String
|
|
||||||
|
|
||||||
Range:
|
|
||||||
start: Position
|
|
||||||
end: Position
|
|
||||||
|
|
||||||
Position:
|
|
||||||
line: int
|
|
||||||
character: int
|
|
||||||
|
|
||||||
Diagnostics:
|
|
||||||
uri: String
|
|
||||||
diagnostics:
|
|
||||||
listType: Diagnostic
|
|
||||||
|
|
||||||
Diagnostic:
|
|
||||||
range: Range
|
|
||||||
severity: int
|
|
||||||
code: dynamic
|
|
||||||
source: String
|
|
||||||
message: String
|
|
||||||
|
|
||||||
CompletionList:
|
|
||||||
isIncomplete: bool
|
|
||||||
items:
|
|
||||||
listType: CompletionItem
|
|
||||||
|
|
||||||
CompletionItem:
|
|
||||||
label: String
|
|
||||||
kind: CompletionItemKind
|
|
||||||
detail: String
|
|
||||||
documentation: String
|
|
||||||
sortText: String
|
|
||||||
filterText: String
|
|
||||||
insertText: String
|
|
||||||
insertTextFormat: InsertTextFormat
|
|
||||||
textEdit: TextEdit
|
|
||||||
additionalTextEdits:
|
|
||||||
listType: TextEdit
|
|
||||||
command: Command
|
|
||||||
data: dynamic
|
|
||||||
|
|
||||||
CompletionItemKind:
|
|
||||||
enumValues:
|
|
||||||
text: 1
|
|
||||||
method: 2
|
|
||||||
function: 3
|
|
||||||
constructor: 4
|
|
||||||
field: 5
|
|
||||||
variable: 6
|
|
||||||
classKind: 7
|
|
||||||
interface: 8
|
|
||||||
module: 9
|
|
||||||
property: 10
|
|
||||||
unit: 11
|
|
||||||
value: 12
|
|
||||||
enumKind: 13
|
|
||||||
keyword: 14
|
|
||||||
snippet: 15
|
|
||||||
color: 16
|
|
||||||
file: 17
|
|
||||||
reference: 18
|
|
||||||
wireType: int
|
|
||||||
|
|
||||||
InsertTextFormat:
|
|
||||||
enumValues:
|
|
||||||
plainText: 1
|
|
||||||
snippet: 2
|
|
||||||
wireType: int
|
|
||||||
|
|
||||||
TextEdit:
|
|
||||||
range: Range
|
|
||||||
newText: String
|
|
||||||
|
|
||||||
Command:
|
|
||||||
title: String
|
|
||||||
command: String
|
|
||||||
arguments:
|
|
||||||
listType: dynamic
|
|
||||||
|
|
||||||
Location:
|
|
||||||
uri: String
|
|
||||||
range: Range
|
|
||||||
|
|
||||||
DynamicRegistrationCapability:
|
|
||||||
dynamicRegistration: bool
|
|
||||||
|
|
||||||
WorkspaceClientCapabilities:
|
|
||||||
applyEdit: bool
|
|
||||||
didChangeConfiguration: DynamicRegistrationCapability
|
|
||||||
didChangeWatchedFiles: DynamicRegistrationCapability
|
|
||||||
symbol: DynamicRegistrationCapability
|
|
||||||
executeCommand: DynamicRegistrationCapability
|
|
||||||
|
|
||||||
SynchronizationCapabilities:
|
|
||||||
dynamicRegistration: bool
|
|
||||||
willSave: bool
|
|
||||||
willSaveWaitUntil: bool
|
|
||||||
didSave: bool
|
|
||||||
|
|
||||||
CompletionItemCapabilities:
|
|
||||||
snippetSupport: bool
|
|
||||||
|
|
||||||
CompletionCapabilities:
|
|
||||||
dynamicRegistration: bool
|
|
||||||
completionItem: CompletionItemCapabilities
|
|
||||||
|
|
||||||
HoverCapabilities:
|
|
||||||
dynamicRegistration: bool
|
|
||||||
contentFormat:
|
|
||||||
listType: String
|
|
||||||
|
|
||||||
CodeActionCapabilities:
|
|
||||||
dynamicRegistration: bool
|
|
||||||
codeActionLiteralSupport: CodeActionLiteralSupport
|
|
||||||
|
|
||||||
CodeActionLiteralSupport:
|
|
||||||
codeActionKind: CodeActionKinds
|
|
||||||
|
|
||||||
CodeActionKinds:
|
|
||||||
valueSet:
|
|
||||||
listType: String # open ended enum
|
|
||||||
|
|
||||||
TextDocumentClientCapabilities:
|
|
||||||
codeAction: CodeActionCapabilities
|
|
||||||
completion: CompletionCapabilities
|
|
||||||
hover: HoverCapabilities
|
|
||||||
synchronization: SynchronizationCapabilities
|
|
||||||
codeLens: DynamicRegistrationCapability
|
|
||||||
definition: DynamicRegistrationCapability
|
|
||||||
documentHighlight: DynamicRegistrationCapability
|
|
||||||
documentLink: DynamicRegistrationCapability
|
|
||||||
documentSymbol: DynamicRegistrationCapability
|
|
||||||
formatting: DynamicRegistrationCapability
|
|
||||||
onTypeFormatting: DynamicRegistrationCapability
|
|
||||||
references: DynamicRegistrationCapability
|
|
||||||
rename: DynamicRegistrationCapability
|
|
||||||
|
|
||||||
ClientCapabilities:
|
|
||||||
workspace: WorkspaceClientCapabilities
|
|
||||||
textDocument: TextDocumentClientCapabilities
|
|
||||||
|
|
||||||
TextDocumentSyncKind:
|
|
||||||
enumValues:
|
|
||||||
none: 0
|
|
||||||
full: 1
|
|
||||||
incremental: 2
|
|
||||||
wireType: int
|
|
||||||
|
|
||||||
CompletionOptions:
|
|
||||||
resolveProvider: bool
|
|
||||||
triggerCharacters:
|
|
||||||
listType: String
|
|
||||||
|
|
||||||
SignatureHelpOptions:
|
|
||||||
triggerCharacters:
|
|
||||||
listType: String
|
|
||||||
|
|
||||||
CodeLensOptions:
|
|
||||||
resolveProvider: bool
|
|
||||||
|
|
||||||
DocumentOnTypeFormattingOptions:
|
|
||||||
firstTriggerCharacter: String
|
|
||||||
moreTriggerCharacter:
|
|
||||||
listType: String
|
|
||||||
|
|
||||||
DocumentLinkOptions:
|
|
||||||
resolveProvider: bool
|
|
||||||
|
|
||||||
ExecuteCommandOptions:
|
|
||||||
commands:
|
|
||||||
listType: String
|
|
||||||
|
|
||||||
SaveOptions:
|
|
||||||
includeText: bool
|
|
||||||
|
|
||||||
TextDocumentSyncOptions:
|
|
||||||
openClose: bool
|
|
||||||
change: TextDocumentSyncKind
|
|
||||||
willSave: bool
|
|
||||||
willSaveWaitUntil: bool
|
|
||||||
save: SaveOptions
|
|
||||||
|
|
||||||
ServerCapabilities:
|
|
||||||
codeActionProvider: bool
|
|
||||||
codeLensProvider: CodeLensOptions
|
|
||||||
completionProvider: CompletionOptions
|
|
||||||
definitionProvider: bool
|
|
||||||
documentFormattingProvider: bool
|
|
||||||
documentHighlightProvider: bool
|
|
||||||
documentLinkProvider: DocumentLinkOptions
|
|
||||||
documentOnTypeFormattingProvider: DocumentOnTypeFormattingOptions
|
|
||||||
documentRangeFormattingProvider: bool
|
|
||||||
documentSymbolProvider: bool
|
|
||||||
executeCommandProvider: ExecuteCommandOptions
|
|
||||||
hoverProvider: bool
|
|
||||||
implementationProvider: bool
|
|
||||||
referencesProvider: bool
|
|
||||||
renameProvider: bool
|
|
||||||
signatureHelpProvider: SignatureHelpOptions
|
|
||||||
textDocumentSync: TextDocumentSyncOptions
|
|
||||||
workspaceSymbolProvider: bool
|
|
||||||
|
|
||||||
ReferenceContext:
|
|
||||||
includeDeclaration: bool
|
|
||||||
|
|
||||||
Hover:
|
|
||||||
contents: String
|
|
||||||
range: Range
|
|
||||||
|
|
||||||
HoverMarkup:
|
|
||||||
contents: MarkupContent
|
|
||||||
range: Range
|
|
||||||
|
|
||||||
CodeActionContext:
|
|
||||||
diagnostics:
|
|
||||||
listType: Diagnostic
|
|
||||||
|
|
||||||
CodeAction:
|
|
||||||
title: String
|
|
||||||
kind: String
|
|
||||||
diagnostics:
|
|
||||||
listType: Diagnostic
|
|
||||||
edit: WorkspaceEdit
|
|
||||||
command: Command
|
|
||||||
|
|
||||||
ApplyWorkspaceEditParams:
|
|
||||||
label: String
|
|
||||||
edit: WorkspaceEdit
|
|
||||||
|
|
||||||
WorkspaceEdit:
|
|
||||||
# Not using `documentChanges` since there is no reasonable way to support text
|
|
||||||
# document version
|
|
||||||
changes:
|
|
||||||
mapType:
|
|
||||||
listType: TextEdit
|
|
||||||
|
|
||||||
DocumentHighlight:
|
|
||||||
range: Range
|
|
||||||
kind: DocumentHighlightKind
|
|
||||||
|
|
||||||
DocumentHighlightKind:
|
|
||||||
enumValues:
|
|
||||||
text: 1
|
|
||||||
read: 2
|
|
||||||
write: 3
|
|
||||||
wireType: int
|
|
||||||
|
|
||||||
SymbolInformation:
|
|
||||||
name: String
|
|
||||||
kind: SymbolKind
|
|
||||||
location: Location
|
|
||||||
containerName: String
|
|
||||||
|
|
||||||
SymbolKind:
|
|
||||||
enumValues:
|
|
||||||
file: 1
|
|
||||||
module: 2
|
|
||||||
namespace: 3
|
|
||||||
package: 4
|
|
||||||
classSymbol: 5
|
|
||||||
method: 6
|
|
||||||
property: 7
|
|
||||||
field: 8
|
|
||||||
constructor: 9
|
|
||||||
enumSymbol: 10
|
|
||||||
interface: 11
|
|
||||||
function: 12
|
|
||||||
variable: 13
|
|
||||||
constant: 14
|
|
||||||
string: 15
|
|
||||||
number: 16
|
|
||||||
boolean: 17
|
|
||||||
array: 18
|
|
||||||
object: 19
|
|
||||||
key: 20
|
|
||||||
nullSymbol: 21
|
|
||||||
enumMember: 22
|
|
||||||
struct: 23
|
|
||||||
event: 24
|
|
||||||
operator: 25
|
|
||||||
typeParameter: 26
|
|
||||||
wireType: int
|
|
||||||
|
|
||||||
MarkupContentKind:
|
|
||||||
enumValues:
|
|
||||||
plaintext: 'plaintext'
|
|
||||||
markdown: 'markdown'
|
|
||||||
wireType: String
|
|
||||||
|
|
||||||
MarkupContent:
|
|
||||||
kind: MarkupContentKind
|
|
||||||
value: String
|
|
||||||
|
|
||||||
MessageType:
|
|
||||||
enumValues:
|
|
||||||
error: 1
|
|
||||||
warning: 2
|
|
||||||
info: 3
|
|
||||||
log: 4
|
|
||||||
wireType: int
|
|
||||||
|
|
||||||
ShowMessageParams:
|
|
||||||
type: MessageType
|
|
||||||
message: String
|
|
|
@ -1,201 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:json_rpc_2/json_rpc_2.dart';
|
|
||||||
|
|
||||||
import 'interface.dart';
|
|
||||||
import 'messages.dart';
|
|
||||||
import 'wireformat.dart';
|
|
||||||
|
|
||||||
/// A Language Server communicating over stdin and stdout.
|
|
||||||
class StdIOLanguageServer {
|
|
||||||
final LanguageServer _server;
|
|
||||||
Future<void> onDone;
|
|
||||||
|
|
||||||
/// Wrap [_server] and register RPC methods using the LSP wire protocol.
|
|
||||||
///
|
|
||||||
/// Methods are guarded against being called before the server is initialized.
|
|
||||||
StdIOLanguageServer.start(this._server) {
|
|
||||||
final peer = Peer(lspChannel(stdin, stdout));
|
|
||||||
|
|
||||||
_lifecycleMethods(peer);
|
|
||||||
_fileHandlingMethods(peer);
|
|
||||||
_notifications(peer);
|
|
||||||
_completionMethods(peer);
|
|
||||||
_referenceMethods(peer);
|
|
||||||
_codeActionMethods(peer);
|
|
||||||
|
|
||||||
_server.setupExtraMethods(peer);
|
|
||||||
|
|
||||||
peer.listen();
|
|
||||||
|
|
||||||
onDone = _server.onDone.then((_) => peer.close()).then((_) => null);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _isInitialized = false;
|
|
||||||
|
|
||||||
void _lifecycleMethods(Peer peer) {
|
|
||||||
peer
|
|
||||||
..registerMethod('initialize', (params) async {
|
|
||||||
final serverCapabilities = await _server.initialize(
|
|
||||||
params['processId'].valueOr(0) as int,
|
|
||||||
params['rootUri'].valueOr('') as String,
|
|
||||||
ClientCapabilities.fromJson(params['capabilities'].value as Map),
|
|
||||||
params['trace'].valueOr('off') as String);
|
|
||||||
_isInitialized = true;
|
|
||||||
return {'capabilities': serverCapabilities.toJson()};
|
|
||||||
})
|
|
||||||
..registerMethod('initialized', (params) => _server.initialized())
|
|
||||||
..registerMethod('shutdown', _server.shutdown)
|
|
||||||
..registerMethod('exit', _server.exit);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Register a request that will throw if throw if used before initialization.
|
|
||||||
void _registerRequest(Peer peer, String methodName, Function callback) {
|
|
||||||
peer.registerMethod(methodName, (params) {
|
|
||||||
if (!_isInitialized) {
|
|
||||||
throw RpcException(-32003, 'The server has not been initialized');
|
|
||||||
}
|
|
||||||
return callback(params);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Notifications are ignored until after initialization.
|
|
||||||
void _registerNotification(Peer peer, String methodName, Function callback) {
|
|
||||||
peer.registerMethod(methodName, (params) {
|
|
||||||
if (_isInitialized) return callback(params);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _fileHandlingMethods(Peer peer) {
|
|
||||||
_registerNotification(peer, 'textDocument/didOpen', (params) {
|
|
||||||
_server.textDocumentDidOpen(_documentItem(params));
|
|
||||||
});
|
|
||||||
_registerNotification(peer, 'textDocument/didChange', (params) {
|
|
||||||
_server.textDocumentDidChange(
|
|
||||||
_versionedDocument(params), _contentChanges(params));
|
|
||||||
});
|
|
||||||
_registerNotification(peer, 'textDocument/didClose', (params) {
|
|
||||||
_server.textDocumentDidClose(_document(params));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _notifications(Peer peer) {
|
|
||||||
_server
|
|
||||||
..diagnostics.map((d) => d.toJson()).forEach((diagnostics) =>
|
|
||||||
peer.sendNotification('textDocument/publishDiagnostics', diagnostics))
|
|
||||||
..workspaceEdits.map((e) => e.toJson()).forEach((edit) {
|
|
||||||
// Ignore response?
|
|
||||||
peer.sendRequest('workspace/applyEdit', edit);
|
|
||||||
})
|
|
||||||
..logMessages.map((e) => e.toJson()).forEach(
|
|
||||||
(message) => peer.sendNotification('window/logMessage', message))
|
|
||||||
..showMessages.map((e) => e.toJson()).forEach(
|
|
||||||
(message) => peer.sendNotification('window/showMessage', message));
|
|
||||||
}
|
|
||||||
|
|
||||||
void _completionMethods(Peer peer) {
|
|
||||||
_registerRequest(
|
|
||||||
peer,
|
|
||||||
'textDocument/completion',
|
|
||||||
(params) => _server
|
|
||||||
.textDocumentCompletion(_document(params), _position(params))
|
|
||||||
.then((r) => r.toJson()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void _referenceMethods(Peer peer) {
|
|
||||||
_registerRequest(
|
|
||||||
peer,
|
|
||||||
'textDocument/definition',
|
|
||||||
(params) => _server
|
|
||||||
.textDocumentDefinition(_document(params), _position(params))
|
|
||||||
.then((r) => r?.toJson()));
|
|
||||||
_registerRequest(
|
|
||||||
peer,
|
|
||||||
'textDocument/hover',
|
|
||||||
(params) => _server
|
|
||||||
.textDocumentHover(_document(params), _position(params))
|
|
||||||
.then((r) => r?.toJson()));
|
|
||||||
_registerRequest(
|
|
||||||
peer,
|
|
||||||
'textDocument/references',
|
|
||||||
(params) => _server
|
|
||||||
.textDocumentReferences(
|
|
||||||
_document(params), _position(params), _referenceContext(params))
|
|
||||||
.then((r) => r?.map((e) => e.toJson())?.toList()));
|
|
||||||
_registerRequest(
|
|
||||||
peer,
|
|
||||||
'textDocument/implementation',
|
|
||||||
(params) => _server
|
|
||||||
.textDocumentImplementation(_document(params), _position(params))
|
|
||||||
.then((r) => r?.map((e) => e.toJson())?.toList()));
|
|
||||||
_registerRequest(
|
|
||||||
peer,
|
|
||||||
'textDocument/documentHighlight',
|
|
||||||
(params) => _server
|
|
||||||
.textDocumentHighlight(_document(params), _position(params))
|
|
||||||
.then((r) => r?.map((e) => e.toJson())?.toList()));
|
|
||||||
_registerRequest(
|
|
||||||
peer,
|
|
||||||
'textDocument/documentSymbol',
|
|
||||||
(params) => _server
|
|
||||||
.textDocumentSymbols(_document(params))
|
|
||||||
.then((r) => r?.map((e) => e.toJson())?.toList()));
|
|
||||||
_registerRequest(
|
|
||||||
peer,
|
|
||||||
'workspace/symbol',
|
|
||||||
(params) => _server
|
|
||||||
.workspaceSymbol(_query(params))
|
|
||||||
.then((r) => r?.map((e) => e.toJson())?.toList()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void _codeActionMethods(Peer peer) {
|
|
||||||
_registerRequest(
|
|
||||||
peer,
|
|
||||||
'textDocument/codeAction',
|
|
||||||
(params) => _server
|
|
||||||
.textDocumentCodeAction(
|
|
||||||
_document(params), _range(params), _codeActionContext(params))
|
|
||||||
.then((r) => r?.map((e) => e.toJson())?.toList()));
|
|
||||||
_registerRequest(
|
|
||||||
peer,
|
|
||||||
'workspace/executeCommand',
|
|
||||||
(params) => _server.workspaceExecuteCommand(
|
|
||||||
params['command'].value as String,
|
|
||||||
params['arguments']?.value as List));
|
|
||||||
_registerRequest(
|
|
||||||
peer,
|
|
||||||
'textDocument/rename',
|
|
||||||
(params) async => (await _server.textDocumentRename(_document(params),
|
|
||||||
_position(params), params['newName'].value as String))
|
|
||||||
.toJson());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TextDocumentItem _documentItem(params) =>
|
|
||||||
TextDocumentItem.fromJson(params['textDocument'].value as Map);
|
|
||||||
|
|
||||||
VersionedTextDocumentIdentifier _versionedDocument(params) =>
|
|
||||||
VersionedTextDocumentIdentifier.fromJson(
|
|
||||||
params['textDocument'].value as Map);
|
|
||||||
|
|
||||||
TextDocumentIdentifier _document(params) =>
|
|
||||||
TextDocumentIdentifier.fromJson(params['textDocument'].value as Map);
|
|
||||||
|
|
||||||
Range _range(params) => Range.fromJson(params['range'].value as Map);
|
|
||||||
|
|
||||||
Position _position(params) =>
|
|
||||||
Position.fromJson(params['position'].value as Map);
|
|
||||||
|
|
||||||
CodeActionContext _codeActionContext(params) =>
|
|
||||||
CodeActionContext.fromJson(params['context'].value as Map);
|
|
||||||
|
|
||||||
ReferenceContext _referenceContext(params) =>
|
|
||||||
ReferenceContext.fromJson(params['context'].value as Map);
|
|
||||||
|
|
||||||
List<TextDocumentContentChangeEvent> _contentChanges(params) =>
|
|
||||||
(params['contentChanges'].value as Iterable)
|
|
||||||
.map((change) => TextDocumentContentChangeEvent.fromJson(change as Map))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
String _query(params) => params['query'].value as String;
|
|
|
@ -1,98 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:stream_channel/stream_channel.dart';
|
|
||||||
import 'package:async/async.dart';
|
|
||||||
|
|
||||||
StreamChannel<String> lspChannel(
|
|
||||||
Stream<List<int>> stream, StreamSink<List<int>> sink) {
|
|
||||||
final parser = _Parser(stream);
|
|
||||||
final outSink = StreamSinkTransformer.fromHandlers(
|
|
||||||
handleData: _serialize,
|
|
||||||
handleDone: (sink) {
|
|
||||||
sink.close();
|
|
||||||
parser.close();
|
|
||||||
}).bind(sink);
|
|
||||||
return StreamChannel.withGuarantees(parser.stream, outSink);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _serialize(String data, EventSink<List<int>> sink) {
|
|
||||||
final message = utf8.encode(data);
|
|
||||||
final header = 'Content-Length: ${message.length}\r\n\r\n';
|
|
||||||
sink.add(ascii.encode(header));
|
|
||||||
for (var chunk in _chunks(message, 1024)) {
|
|
||||||
sink.add(chunk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _Parser {
|
|
||||||
final _streamCtl = StreamController<String>();
|
|
||||||
Stream<String> get stream => _streamCtl.stream;
|
|
||||||
|
|
||||||
final _buffer = <int>[];
|
|
||||||
bool _headerMode = true;
|
|
||||||
int _contentLength = -1;
|
|
||||||
|
|
||||||
StreamSubscription _subscription;
|
|
||||||
|
|
||||||
_Parser(Stream<List<int>> stream) {
|
|
||||||
_subscription =
|
|
||||||
stream.expand((bytes) => bytes).listen(_handleByte, onDone: () {
|
|
||||||
_streamCtl.close();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> close() => _subscription.cancel();
|
|
||||||
|
|
||||||
void _handleByte(int byte) {
|
|
||||||
_buffer.add(byte);
|
|
||||||
if (_headerMode && _headerComplete) {
|
|
||||||
_contentLength = _parseContentLength();
|
|
||||||
_buffer.clear();
|
|
||||||
_headerMode = false;
|
|
||||||
} else if (!_headerMode && _messageComplete) {
|
|
||||||
_streamCtl.add(utf8.decode(_buffer));
|
|
||||||
_buffer.clear();
|
|
||||||
_headerMode = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether the entire message is in [_buffer].
|
|
||||||
bool get _messageComplete => _buffer.length >= _contentLength;
|
|
||||||
|
|
||||||
/// Decodes [_buffer] into a String and looks for the 'Content-Length' header.
|
|
||||||
int _parseContentLength() {
|
|
||||||
final asString = ascii.decode(_buffer);
|
|
||||||
final headers = asString.split('\r\n');
|
|
||||||
final lengthHeader =
|
|
||||||
headers.firstWhere((h) => h.startsWith('Content-Length'));
|
|
||||||
final length = lengthHeader.split(':').last.trim();
|
|
||||||
return int.parse(length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether [_buffer] ends in '\r\n\r\n'.
|
|
||||||
bool get _headerComplete {
|
|
||||||
final l = _buffer.length;
|
|
||||||
return l > 4 &&
|
|
||||||
_buffer[l - 1] == 10 &&
|
|
||||||
_buffer[l - 2] == 13 &&
|
|
||||||
_buffer[l - 3] == 10 &&
|
|
||||||
_buffer[l - 4] == 13;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Iterable<List<T>> _chunks<T>(List<T> data, int chunkSize) sync* {
|
|
||||||
if (data.length <= chunkSize) {
|
|
||||||
yield data;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var low = 0;
|
|
||||||
while (low < data.length) {
|
|
||||||
if (data.length > low + chunkSize) {
|
|
||||||
yield data.sublist(low, low + chunkSize);
|
|
||||||
} else {
|
|
||||||
yield data.sublist(low);
|
|
||||||
}
|
|
||||||
low += chunkSize;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,543 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
//import 'package:dart_language_server/src/protocol/language_server/interface.dart';
|
|
||||||
//import 'package:dart_language_server/src/protocol/language_server/messages.dart';
|
|
||||||
import 'package:file/file.dart';
|
|
||||||
import 'package:file/local.dart';
|
|
||||||
import 'package:file/memory.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:path/path.dart' as p;
|
|
||||||
import 'package:source_span/source_span.dart';
|
|
||||||
import 'package:string_scanner/string_scanner.dart';
|
|
||||||
import 'package:symbol_table/symbol_table.dart';
|
|
||||||
|
|
||||||
import 'protocol/language_server/interface.dart';
|
|
||||||
import 'protocol/language_server/messages.dart';
|
|
||||||
|
|
||||||
import './analyzer.dart';
|
|
||||||
import './formatter.dart' as fmt;
|
|
||||||
import './object.dart';
|
|
||||||
|
|
||||||
class JaelLanguageServer extends LanguageServer {
|
|
||||||
var _diagnostics = new StreamController<Diagnostics>();
|
|
||||||
var _done = new Completer();
|
|
||||||
var _memFs = new MemoryFileSystem();
|
|
||||||
var _localFs = const LocalFileSystem();
|
|
||||||
Directory _localRootDir, _memRootDir;
|
|
||||||
var logger = new Logger('jael');
|
|
||||||
Uri _rootUri;
|
|
||||||
var _workspaceEdits = new StreamController<ApplyWorkspaceEditParams>();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Stream<Diagnostics> get diagnostics => _diagnostics.stream;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> get onDone => _done.future;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Stream<ApplyWorkspaceEditParams> get workspaceEdits => _workspaceEdits.stream;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> shutdown() {
|
|
||||||
if (!_done.isCompleted) _done.complete();
|
|
||||||
_diagnostics.close();
|
|
||||||
_workspaceEdits.close();
|
|
||||||
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
|
|
||||||
Future<ServerCapabilities> initialize(int clientPid, String rootUri,
|
|
||||||
ClientCapabilities clientCapabilities, String trace) async {
|
|
||||||
// Find our real root dir.
|
|
||||||
_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.
|
|
||||||
await for (var entity in _localRootDir.list(recursive: true)) {
|
|
||||||
if (entity is File && p.extension(entity.path) == '.jael') {
|
|
||||||
logger.info('HEY ${entity.path}');
|
|
||||||
var file = _memFs.file(entity.absolute.path);
|
|
||||||
await file.create(recursive: true);
|
|
||||||
await entity.openRead().pipe(file.openWrite(mode: FileMode.write));
|
|
||||||
logger.info(
|
|
||||||
'Found Jael file ${file.path}; copied to ${file.absolute.path}');
|
|
||||||
|
|
||||||
// Analyze it
|
|
||||||
var documentId = new TextDocumentIdentifier((b) {
|
|
||||||
b..uri = _rootUri.replace(path: file.path).toString();
|
|
||||||
});
|
|
||||||
|
|
||||||
await analyzerForId(documentId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ServerCapabilities((b) {
|
|
||||||
b
|
|
||||||
..codeActionProvider = false
|
|
||||||
..completionProvider = new CompletionOptions((b) {
|
|
||||||
b
|
|
||||||
..resolveProvider = true
|
|
||||||
..triggerCharacters =
|
|
||||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdeghijklmnopqrstuvxwyz'
|
|
||||||
.codeUnits
|
|
||||||
.map((c) => new String.fromCharCode(c))
|
|
||||||
.toList();
|
|
||||||
})
|
|
||||||
..definitionProvider = true
|
|
||||||
..documentHighlightProvider = true
|
|
||||||
..documentRangeFormattingProvider = false
|
|
||||||
..documentOnTypeFormattingProvider = null
|
|
||||||
..documentSymbolProvider = true
|
|
||||||
..documentFormattingProvider = true
|
|
||||||
..hoverProvider = true
|
|
||||||
..implementationProvider = true
|
|
||||||
..referencesProvider = true
|
|
||||||
..renameProvider = true
|
|
||||||
..signatureHelpProvider = new SignatureHelpOptions((b) {})
|
|
||||||
..textDocumentSync = new TextDocumentSyncOptions((b) {
|
|
||||||
b
|
|
||||||
..openClose = true
|
|
||||||
..change = TextDocumentSyncKind.full
|
|
||||||
..save = new SaveOptions((b) {
|
|
||||||
b..includeText = false;
|
|
||||||
})
|
|
||||||
..willSave = false
|
|
||||||
..willSaveWaitUntil = false;
|
|
||||||
})
|
|
||||||
..workspaceSymbolProvider = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<File> fileForId(TextDocumentIdentifier documentId) async {
|
|
||||||
var uri = Uri.parse(documentId.uri);
|
|
||||||
var relativePath = uri.path;
|
|
||||||
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()) {
|
|
||||||
await file.create(recursive: true);
|
|
||||||
await _localFs.file(uri).openRead().pipe(file.openWrite());
|
|
||||||
logger.info('Opened Jael file ${file.path}');
|
|
||||||
}
|
|
||||||
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Scanner> scannerForId(TextDocumentIdentifier documentId) async {
|
|
||||||
var file = await fileForId(documentId);
|
|
||||||
return scan(await file.readAsString(), sourceUrl: file.uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Analyzer> analyzerForId(TextDocumentIdentifier documentId) async {
|
|
||||||
var scanner = await scannerForId(documentId);
|
|
||||||
var analyzer = new Analyzer(scanner, logger)..errors.addAll(scanner.errors);
|
|
||||||
analyzer.parseDocument();
|
|
||||||
emitDiagnostics(documentId.uri, analyzer.errors.map(toDiagnostic).toList());
|
|
||||||
return analyzer;
|
|
||||||
}
|
|
||||||
|
|
||||||
Diagnostic toDiagnostic(JaelError e) {
|
|
||||||
return new Diagnostic((b) {
|
|
||||||
b
|
|
||||||
..message = e.message
|
|
||||||
..range = toRange(e.span)
|
|
||||||
..severity = toSeverity(e.severity)
|
|
||||||
..source = e.span.start.sourceUrl.toString();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
int toSeverity(JaelErrorSeverity s) {
|
|
||||||
switch (s) {
|
|
||||||
case JaelErrorSeverity.warning:
|
|
||||||
return DiagnosticSeverity.warning;
|
|
||||||
default:
|
|
||||||
return DiagnosticSeverity.error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Range toRange(FileSpan span) {
|
|
||||||
return new Range((b) {
|
|
||||||
b
|
|
||||||
..start = toPosition(span.start)
|
|
||||||
..end = toPosition(span.end);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Range emptyRange() {
|
|
||||||
return new Range((b) => b
|
|
||||||
..start = b.end = new Position((b) {
|
|
||||||
b
|
|
||||||
..character = 1
|
|
||||||
..line = 0;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
Position toPosition(SourceLocation location) {
|
|
||||||
return new Position((b) {
|
|
||||||
b
|
|
||||||
..line = location.line
|
|
||||||
..character = location.column;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Location toLocation(String uri, FileSpan span) {
|
|
||||||
return new Location((b) {
|
|
||||||
b
|
|
||||||
..range = toRange(span)
|
|
||||||
..uri = uri;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isReachable(JaelObject obj, Position position) {
|
|
||||||
return obj.span.start.line <= position.line &&
|
|
||||||
obj.span.start.column <= position.character;
|
|
||||||
}
|
|
||||||
|
|
||||||
CompletionItem toCompletion(Variable<JaelObject> symbol) {
|
|
||||||
var value = symbol.value;
|
|
||||||
|
|
||||||
if (value is JaelCustomElement) {
|
|
||||||
var name = value.name;
|
|
||||||
return new CompletionItem((b) {
|
|
||||||
b
|
|
||||||
..kind = CompletionItemKind.classKind
|
|
||||||
..label = symbol.name
|
|
||||||
..textEdit = new TextEdit((b) {
|
|
||||||
b
|
|
||||||
..range = emptyRange()
|
|
||||||
..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;
|
|
||||||
}
|
|
||||||
|
|
||||||
void emitDiagnostics(String uri, Iterable<Diagnostic> diagnostics) {
|
|
||||||
_diagnostics.add(new Diagnostics((b) {
|
|
||||||
logger.info('$uri => ${diagnostics.map((d) => d.message).toList()}');
|
|
||||||
b
|
|
||||||
..diagnostics = diagnostics.toList()
|
|
||||||
..uri = uri.toString();
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future textDocumentDidOpen(TextDocumentItem document) async {
|
|
||||||
await analyzerForId(
|
|
||||||
new TextDocumentIdentifier((b) => b..uri = document.uri));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future textDocumentDidChange(VersionedTextDocumentIdentifier documentId,
|
|
||||||
List<TextDocumentContentChangeEvent> changes) async {
|
|
||||||
var id = new TextDocumentIdentifier((b) => b..uri = documentId.uri);
|
|
||||||
var file = await fileForId(id);
|
|
||||||
|
|
||||||
for (var change in changes) {
|
|
||||||
if (change.text != null) {
|
|
||||||
await file.writeAsString(change.text);
|
|
||||||
} else if (change.range != null) {
|
|
||||||
var contents = await file.readAsString();
|
|
||||||
|
|
||||||
int findIndex(Position position) {
|
|
||||||
var lines = contents.split('\n');
|
|
||||||
|
|
||||||
// Sum the length of the previous lines.
|
|
||||||
int lineLength = lines
|
|
||||||
.take(position.line - 1)
|
|
||||||
.map((s) => s.length)
|
|
||||||
.reduce((a, b) => a + b);
|
|
||||||
return lineLength + position.character - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (change.range == null) {
|
|
||||||
contents = change.text;
|
|
||||||
} else {
|
|
||||||
var start = findIndex(change.range.start),
|
|
||||||
end = findIndex(change.range.end);
|
|
||||||
contents = contents.replaceRange(start, end, change.text);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info('${file.path} => $contents');
|
|
||||||
await file.writeAsString(contents);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await analyzerForId(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List> textDocumentCodeAction(TextDocumentIdentifier documentId,
|
|
||||||
Range range, CodeActionContext context) async {
|
|
||||||
// TODO: implement textDocumentCodeAction
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<CompletionList> textDocumentCompletion(
|
|
||||||
TextDocumentIdentifier documentId, Position position) async {
|
|
||||||
var analyzer = await analyzerForId(documentId);
|
|
||||||
var symbols = analyzer.scope.allVariables;
|
|
||||||
var reachable = symbols.where((s) => isReachable(s.value, position));
|
|
||||||
return new CompletionList((b) {
|
|
||||||
b
|
|
||||||
..isIncomplete = false
|
|
||||||
..items = reachable.map(toCompletion).toList();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
// First, read the file.
|
|
||||||
var file = await fileForId(documentId);
|
|
||||||
var contents = await file.readAsString();
|
|
||||||
|
|
||||||
// Next, find the current index.
|
|
||||||
var scanner = new SpanScanner(contents);
|
|
||||||
|
|
||||||
while (!scanner.isDone &&
|
|
||||||
(scanner.state.line != position.line ||
|
|
||||||
scanner.state.column != position.character)) {
|
|
||||||
scanner.readChar();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
Future<Location> textDocumentDefinition(
|
|
||||||
TextDocumentIdentifier documentId, Position position) async {
|
|
||||||
var symbol = await currentSymbol(documentId, position);
|
|
||||||
if (symbol != null) {
|
|
||||||
return toLocation(documentId.uri, symbol.span);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<DocumentHighlight>> textDocumentHighlight(
|
|
||||||
TextDocumentIdentifier documentId, Position position) async {
|
|
||||||
var symbol = await currentSymbol(documentId, position);
|
|
||||||
if (symbol != null) {
|
|
||||||
return symbol.usages.map((u) {
|
|
||||||
return new DocumentHighlight((b) {
|
|
||||||
b
|
|
||||||
..range = toRange(u.span)
|
|
||||||
..kind = u.type == SymbolUsageType.definition
|
|
||||||
? DocumentHighlightKind.write
|
|
||||||
: DocumentHighlightKind.read;
|
|
||||||
});
|
|
||||||
}).toList();
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Hover> textDocumentHover(
|
|
||||||
TextDocumentIdentifier documentId, Position position) async {
|
|
||||||
var symbol = await currentSymbol(documentId, position);
|
|
||||||
if (symbol != null) {
|
|
||||||
return new Hover((b) {
|
|
||||||
b
|
|
||||||
..contents = symbol.span.text
|
|
||||||
..range = toRange(symbol.span);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<Location>> textDocumentImplementation(
|
|
||||||
TextDocumentIdentifier documentId, Position position) async {
|
|
||||||
var defn = await textDocumentDefinition(documentId, position);
|
|
||||||
return defn == null ? [] : [defn];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<Location>> textDocumentReferences(
|
|
||||||
TextDocumentIdentifier documentId,
|
|
||||||
Position position,
|
|
||||||
ReferenceContext context) async {
|
|
||||||
var symbol = await currentSymbol(documentId, position);
|
|
||||||
if (symbol != null) {
|
|
||||||
return symbol.usages.map((u) {
|
|
||||||
return toLocation(documentId.uri, u.span);
|
|
||||||
}).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<WorkspaceEdit> textDocumentRename(TextDocumentIdentifier documentId,
|
|
||||||
Position position, String newName) async {
|
|
||||||
var symbol = await currentSymbol(documentId, position);
|
|
||||||
if (symbol != null) {
|
|
||||||
return new WorkspaceEdit((b) {
|
|
||||||
b
|
|
||||||
..changes = {
|
|
||||||
symbol.name: symbol.usages.map((u) {
|
|
||||||
return new TextEdit((b) {
|
|
||||||
b
|
|
||||||
..range = toRange(u.span)
|
|
||||||
..newText = (symbol is JaelCustomElement &&
|
|
||||||
u.type == SymbolUsageType.definition)
|
|
||||||
? '"$newName"'
|
|
||||||
: newName;
|
|
||||||
});
|
|
||||||
}).toList()
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return new WorkspaceEdit((b) {
|
|
||||||
b..changes = {};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<SymbolInformation>> textDocumentSymbols(
|
|
||||||
TextDocumentIdentifier documentId) async {
|
|
||||||
var analyzer = await analyzerForId(documentId);
|
|
||||||
return analyzer.allDefinitions.map((symbol) {
|
|
||||||
return new SymbolInformation((b) {
|
|
||||||
b
|
|
||||||
..kind = SymbolKind.classSymbol
|
|
||||||
..name = symbol.name
|
|
||||||
..location = toLocation(documentId.uri, symbol.value.span);
|
|
||||||
});
|
|
||||||
}).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> workspaceExecuteCommand(String command, List arguments) async {
|
|
||||||
// TODO: implement workspaceExecuteCommand
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<SymbolInformation>> workspaceSymbol(String query) async {
|
|
||||||
var values = <JaelObject>[];
|
|
||||||
|
|
||||||
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 = fmt.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 {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
name: vs_jael_language_server
|
|
||||||
version: 0.0.0
|
|
||||||
description: Language Server Protocol implementation for the Jael templating engine.
|
|
||||||
author: Tobe Osakwe <thosakwe@gmail.com>
|
|
||||||
homepage: https://github.com/angel-dart/vscode
|
|
||||||
environment:
|
|
||||||
sdk: ">=2.10.0 <2.12.0"
|
|
||||||
dependencies:
|
|
||||||
args: ^1.0.0
|
|
||||||
# dart_language_server: ^0.1.16
|
|
||||||
file: ^5.0.0
|
|
||||||
io: ^0.3.2
|
|
||||||
jael: #^2.0.0
|
|
||||||
path: ../../jael/jael
|
|
||||||
jael_preprocessor: #^2.0.0
|
|
||||||
path: ../../jael/jael_preprocessor
|
|
||||||
json_rpc_2: ^2.0.0
|
|
||||||
logging: ^0.11.3
|
|
||||||
path: ^1.0.0
|
|
||||||
source_span: ^1.0.0
|
|
||||||
string_scanner: ^1.0.0
|
|
||||||
symbol_table: ^2.0.0
|
|
||||||
executables:
|
|
||||||
jael_language_server: jael_language_server
|
|
Loading…
Reference in a new issue