diff --git a/lsp-embedded-language-service/.gitignore b/lsp-embedded-language-service/.gitignore new file mode 100644 index 000000000..bf962da1f --- /dev/null +++ b/lsp-embedded-language-service/.gitignore @@ -0,0 +1,4 @@ +out +node_modules +client/server +.vscode-test \ No newline at end of file diff --git a/lsp-embedded-language-service/.vscode/launch.json b/lsp-embedded-language-service/.vscode/launch.json new file mode 100644 index 000000000..99b82799a --- /dev/null +++ b/lsp-embedded-language-service/.vscode/launch.json @@ -0,0 +1,44 @@ +// A launch configuration that compiles the extension and then opens it inside a new window +{ + "version": "0.2.0", + "configurations": [ + { + "type": "extensionHost", + "request": "launch", + "name": "Launch Client", + "runtimeExecutable": "${execPath}", + "args": ["--extensionDevelopmentPath=${workspaceRoot}"], + "outFiles": ["${workspaceRoot}/client/out/**/*.js"], + "preLaunchTask": { + "type": "npm", + "script": "watch" + } + }, + { + "type": "node", + "request": "attach", + "name": "Attach to Server", + "port": 6009, + "restart": true, + "outFiles": ["${workspaceRoot}/server/out/**/*.js"] + }, + { + "name": "Language Server E2E Test", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "--extensionDevelopmentPath=${workspaceRoot}", + "--extensionTestsPath=${workspaceRoot}/client/out/test", + "${workspaceRoot}/client/testFixture" + ], + "outFiles": ["${workspaceRoot}/client/out/test/**/*.js"] + } + ], + "compounds": [ + { + "name": "Client + Server", + "configurations": ["Launch Client", "Attach to Server"] + } + ] +} diff --git a/lsp-embedded-language-service/.vscode/settings.json b/lsp-embedded-language-service/.vscode/settings.json new file mode 100644 index 000000000..4a9bab7c8 --- /dev/null +++ b/lsp-embedded-language-service/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "editor.insertSpaces": false, + "tslint.enable": true, + "typescript.tsc.autoDetect": "off", + "typescript.preferences.quoteStyle": "single" +} \ No newline at end of file diff --git a/lsp-embedded-language-service/.vscode/tasks.json b/lsp-embedded-language-service/.vscode/tasks.json new file mode 100644 index 000000000..5efd80488 --- /dev/null +++ b/lsp-embedded-language-service/.vscode/tasks.json @@ -0,0 +1,33 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "compile", + "group": "build", + "presentation": { + "panel": "dedicated", + "reveal": "never" + }, + "problemMatcher": [ + "$tsc" + ] + }, + { + "type": "npm", + "script": "watch", + "isBackground": true, + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "panel": "dedicated", + "reveal": "never" + }, + "problemMatcher": [ + "$tsc-watch" + ] + } + ] +} \ No newline at end of file diff --git a/lsp-embedded-language-service/.vscodeignore b/lsp-embedded-language-service/.vscodeignore new file mode 100644 index 000000000..2ca7fc371 --- /dev/null +++ b/lsp-embedded-language-service/.vscodeignore @@ -0,0 +1,14 @@ +.vscode/** +**/*.ts +**/*.map +.gitignore +**/tsconfig.json +**/tsconfig.base.json +contributing.md +.travis.yml +client/node_modules/** +!client/node_modules/vscode-jsonrpc/** +!client/node_modules/vscode-languageclient/** +!client/node_modules/vscode-languageserver-protocol/** +!client/node_modules/vscode-languageserver-types/** +!client/node_modules/semver/** diff --git a/lsp-embedded-language-service/README.md b/lsp-embedded-language-service/README.md new file mode 100644 index 000000000..9954390d6 --- /dev/null +++ b/lsp-embedded-language-service/README.md @@ -0,0 +1,38 @@ +# LSP Example for Embedded Language using Language Service + +Heavily documented sample code for https://code.visualstudio.com/api/language-extensions/embedded-language-server-guide + +## Functionality + +This Language Server works for plain text file. It has the following language features: +- Completions +- Diagnostics regenerated on each file change or configuration change + +It also includes an End-to-End test. + +## Structure + +``` +. +├── client // Language Client +│ ├── src +│ │ ├── test // End to End tests for Language Client / Server +│ │ └── extension.ts // Language Client entry point +├── package.json // The extension manifest. +└── server // Language Server + └── src + └── server.ts // Language Server entry point +``` + +## Running the Sample + +- Run `npm install` in this folder. This installs all necessary npm modules in both the client and server folder +- Open VS Code on this folder. +- Press Ctrl+Shift+B to compile the client and server. +- Switch to the Debug viewlet. +- Select `Launch Client` from the drop down. +- Run the launch config. +- If you want to debug the server as well use the launch configuration `Attach to Server` +- In the [Extension Development Host] instance of VSCode, open a document in 'plain text' language mode. + - Type `j` or `t` to see `Javascript` and `TypeScript` completion. + - Enter text content such as `AAA aaa BBB`. The extension will emit diagnostics for all words in all-uppercase. diff --git a/lsp-embedded-language-service/client/package-lock.json b/lsp-embedded-language-service/client/package-lock.json new file mode 100644 index 000000000..b8dd9413c --- /dev/null +++ b/lsp-embedded-language-service/client/package-lock.json @@ -0,0 +1,744 @@ +{ + "name": "lsp-sample-client", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "ajv": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "dev": true + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, + "requires": { + "es6-promise": "^4.0.3" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dev": true, + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "http-proxy-agent": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", + "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", + "dev": true, + "requires": { + "agent-base": "4", + "debug": "3.1.0" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-proxy-agent": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", + "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", + "dev": true, + "requires": { + "agent-base": "^4.1.0", + "debug": "^3.1.0" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", + "dev": true + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "dev": true, + "requires": { + "mime-db": "1.40.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "dev": true, + "requires": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" + }, + "dependencies": { + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "psl": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.2.0.tgz", + "integrity": "sha512-GEn74ZffufCmkDDLNcl3uuyF/aSD6exEyh1v/ZSdAomB82t6G9hzJVRx0jBmLDW+VfZqks3aScmMw9DszwUalA==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "querystringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", + "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==", + "dev": true + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", + "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "url-parse": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", + "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", + "dev": true, + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "vscode": { + "version": "1.1.35", + "resolved": "https://registry.npmjs.org/vscode/-/vscode-1.1.35.tgz", + "integrity": "sha512-xPnxzQU40LOS2yPyzWW+WKpTV6qA3z16TcgpZ9O38UWLA157Zz4GxUx5H7Gd07pxzw0GqvusbF4D+5GBgNxvEQ==", + "dev": true, + "requires": { + "glob": "^7.1.2", + "mocha": "^5.2.0", + "request": "^2.88.0", + "semver": "^5.4.1", + "source-map-support": "^0.5.0", + "url-parse": "^1.4.4", + "vscode-test": "^0.4.1" + } + }, + "vscode-jsonrpc": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-4.0.0.tgz", + "integrity": "sha512-perEnXQdQOJMTDFNv+UF3h1Y0z4iSiaN9jIlb0OqIYgosPCZGYh/MCUlkFtV2668PL69lRDO32hmvL2yiidUYg==" + }, + "vscode-languageclient": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-5.2.1.tgz", + "integrity": "sha512-7jrS/9WnV0ruqPamN1nE7qCxn0phkH5LjSgSp9h6qoJGoeAKzwKz/PF6M+iGA/aklx4GLZg1prddhEPQtuXI1Q==", + "requires": { + "semver": "^5.5.0", + "vscode-languageserver-protocol": "3.14.1" + } + }, + "vscode-languageserver-protocol": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.14.1.tgz", + "integrity": "sha512-IL66BLb2g20uIKog5Y2dQ0IiigW0XKrvmWiOvc0yXw80z3tMEzEnHjaGAb3ENuU7MnQqgnYJ1Cl2l9RvNgDi4g==", + "requires": { + "vscode-jsonrpc": "^4.0.0", + "vscode-languageserver-types": "3.14.0" + } + }, + "vscode-languageserver-types": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.14.0.tgz", + "integrity": "sha512-lTmS6AlAlMHOvPQemVwo3CezxBp0sNB95KNPkqp3Nxd5VFEnuG1ByM0zlRWos0zjO3ZWtkvhal0COgiV1xIA4A==" + }, + "vscode-test": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/vscode-test/-/vscode-test-0.4.3.tgz", + "integrity": "sha512-EkMGqBSefZH2MgW65nY05rdRSko15uvzq4VAPM5jVmwYuFQKE7eikKXNJDRxL+OITXHB6pI+a3XqqD32Y3KC5w==", + "dev": true, + "requires": { + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.1" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + } + } +} diff --git a/lsp-embedded-language-service/client/package.json b/lsp-embedded-language-service/client/package.json new file mode 100644 index 000000000..405630780 --- /dev/null +++ b/lsp-embedded-language-service/client/package.json @@ -0,0 +1,25 @@ +{ + "name": "lsp-sample-client", + "description": "VSCode part of a language server", + "author": "Microsoft Corporation", + "license": "MIT", + "version": "0.0.1", + "publisher": "vscode", + "repository": { + "type": "git", + "url": "https://github.com/Microsoft/vscode-extension-samples" + }, + "engines": { + "vscode": "^1.33.0" + }, + "scripts": { + "update-vscode": "vscode-install", + "postinstall": "vscode-install" + }, + "dependencies": { + "vscode-languageclient": "^5.2.1" + }, + "devDependencies": { + "vscode": "^1.1.35" + } +} diff --git a/lsp-embedded-language-service/client/src/extension.ts b/lsp-embedded-language-service/client/src/extension.ts new file mode 100644 index 000000000..4ecf9d607 --- /dev/null +++ b/lsp-embedded-language-service/client/src/extension.ts @@ -0,0 +1,61 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * ------------------------------------------------------------------------------------------ */ + +import * as path from 'path'; +import { ExtensionContext } from 'vscode'; + +import { + LanguageClient, + LanguageClientOptions, + ServerOptions, + TransportKind +} from 'vscode-languageclient'; + +let client: LanguageClient; + +export function activate(context: ExtensionContext) { + // The server is implemented in node + let serverModule = context.asAbsolutePath( + path.join('server', 'out', 'server.js') + ); + // The debug options for the server + // --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging + let debugOptions = { execArgv: ['--nolazy', '--inspect=6009'] }; + + // If the extension is launched in debug mode then the debug server options are used + // Otherwise the run options are used + let serverOptions: ServerOptions = { + run: { module: serverModule, transport: TransportKind.ipc }, + debug: { + module: serverModule, + transport: TransportKind.ipc, + options: debugOptions + } + }; + + // Options to control the language client + let clientOptions: LanguageClientOptions = { + // Register the server for plain text documents + documentSelector: [{ scheme: 'file', language: 'html' }] + }; + + // Create the language client and start the client. + client = new LanguageClient( + 'languageServerExample', + 'Language Server Example', + serverOptions, + clientOptions + ); + + // Start the client. This will also launch the server + client.start(); +} + +export function deactivate(): Thenable | undefined { + if (!client) { + return undefined; + } + return client.stop(); +} diff --git a/lsp-embedded-language-service/client/tsconfig.json b/lsp-embedded-language-service/client/tsconfig.json new file mode 100644 index 000000000..9e5ff6a24 --- /dev/null +++ b/lsp-embedded-language-service/client/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "outDir": "out", + "rootDir": "src", + "sourceMap": true + }, + "include": ["src"], + "exclude": ["node_modules", ".vscode-test"] +} diff --git a/lsp-embedded-language-service/package-lock.json b/lsp-embedded-language-service/package-lock.json new file mode 100644 index 000000000..1b9c52415 --- /dev/null +++ b/lsp-embedded-language-service/package-lock.json @@ -0,0 +1,323 @@ +{ + "name": "lsp-embedded-language-service-sample", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@types/mocha": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", + "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", + "dev": true + }, + "@types/node": { + "version": "12.11.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.11.7.tgz", + "integrity": "sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "commander": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "resolve": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.1.tgz", + "integrity": "sha512-KuIe4mf++td/eFb6wkaPbMDnP6kObCaEtIDuHOUED6MNUo4K670KZUHuuvYPZDxNF0WVLw49n06M2m2dXphEzA==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "dev": true + }, + "tslint": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.16.0.tgz", + "integrity": "sha512-UxG2yNxJ5pgGwmMzPMYh/CCnCnh0HfPgtlVRDs1ykZklufFBL1ZoTlWFRz2NQjcoEiDoRp+JyT0lhBbbH/obyA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^3.2.0", + "glob": "^7.1.1", + "js-yaml": "^3.13.0", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.29.0" + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "typescript": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.4.tgz", + "integrity": "sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + } + } +} diff --git a/lsp-embedded-language-service/package.json b/lsp-embedded-language-service/package.json new file mode 100644 index 000000000..599c35d5c --- /dev/null +++ b/lsp-embedded-language-service/package.json @@ -0,0 +1,35 @@ +{ + "name": "lsp-embedded-language-service-sample", + "description": "A language server example using language services to support embedded languages", + "author": "Microsoft Corporation", + "license": "MIT", + "version": "1.0.0", + "repository": { + "type": "git", + "url": "https://github.com/Microsoft/vscode-extension-samples" + }, + "publisher": "vscode-samples", + "categories": [], + "keywords": [], + "engines": { + "vscode": "^1.33.0" + }, + "activationEvents": [ + "onLanguage:html" + ], + "main": "./client/out/extension", + "contributes": {}, + "scripts": { + "vscode:prepublish": "cd client && npm run update-vscode && cd .. && npm run compile", + "compile": "tsc -b", + "watch": "tsc -b -w", + "postinstall": "cd client && npm install && cd ../server && npm install && cd ..", + "test": "sh ./scripts/e2e.sh" + }, + "devDependencies": { + "@types/mocha": "^5.2.7", + "@types/node": "^12.11.7", + "tslint": "^5.16.0", + "typescript": "^3.6.4" + } +} diff --git a/lsp-embedded-language-service/server/package-lock.json b/lsp-embedded-language-service/server/package-lock.json new file mode 100644 index 000000000..926960896 --- /dev/null +++ b/lsp-embedded-language-service/server/package-lock.json @@ -0,0 +1,96 @@ +{ + "name": "lsp-sample-server", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "vscode-css-languageservice": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-4.1.1.tgz", + "integrity": "sha512-2r2bYbhscivRu1zqh5kNe8aYpFnfksMYC7wTpKX2HqFsSzSJYXk0sCqPaWsP5ptqz0OFBTXnzx2JlE+Nb5Edgw==", + "requires": { + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-languageserver-types": "^3.15.1", + "vscode-nls": "^4.1.1", + "vscode-uri": "^2.1.1" + }, + "dependencies": { + "vscode-languageserver-types": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz", + "integrity": "sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ==" + }, + "vscode-uri": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-2.1.1.tgz", + "integrity": "sha512-eY9jmGoEnVf8VE8xr5znSah7Qt1P/xsCdErz+g8HYZtJ7bZqKH5E3d+6oVNm1AC/c6IHUDokbmVXKOi4qPAC9A==" + } + } + }, + "vscode-html-languageservice": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-3.0.3.tgz", + "integrity": "sha512-U+upM3gHp3HaF3wXAnUduA6IDKcz6frWS/dTAju3cZVIyZwOLBBFElQVlLH0ycHyMzqUFrjvdv+kEyPAEWfQ/g==", + "requires": { + "vscode-languageserver-types": "^3.15.0-next.2", + "vscode-nls": "^4.1.1", + "vscode-uri": "^2.0.3" + }, + "dependencies": { + "vscode-languageserver-types": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz", + "integrity": "sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ==" + }, + "vscode-uri": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-2.1.1.tgz", + "integrity": "sha512-eY9jmGoEnVf8VE8xr5znSah7Qt1P/xsCdErz+g8HYZtJ7bZqKH5E3d+6oVNm1AC/c6IHUDokbmVXKOi4qPAC9A==" + } + } + }, + "vscode-jsonrpc": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-4.0.0.tgz", + "integrity": "sha512-perEnXQdQOJMTDFNv+UF3h1Y0z4iSiaN9jIlb0OqIYgosPCZGYh/MCUlkFtV2668PL69lRDO32hmvL2yiidUYg==" + }, + "vscode-languageserver": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-5.2.1.tgz", + "integrity": "sha512-GuayqdKZqAwwaCUjDvMTAVRPJOp/SLON3mJ07eGsx/Iq9HjRymhKWztX41rISqDKhHVVyFM+IywICyZDla6U3A==", + "requires": { + "vscode-languageserver-protocol": "3.14.1", + "vscode-uri": "^1.0.6" + } + }, + "vscode-languageserver-protocol": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.14.1.tgz", + "integrity": "sha512-IL66BLb2g20uIKog5Y2dQ0IiigW0XKrvmWiOvc0yXw80z3tMEzEnHjaGAb3ENuU7MnQqgnYJ1Cl2l9RvNgDi4g==", + "requires": { + "vscode-jsonrpc": "^4.0.0", + "vscode-languageserver-types": "3.14.0" + } + }, + "vscode-languageserver-textdocument": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.1.tgz", + "integrity": "sha512-UIcJDjX7IFkck7cSkNNyzIz5FyvpQfY7sdzVy+wkKN/BLaD4DQ0ppXQrKePomCxTS7RrolK1I0pey0bG9eh8dA==" + }, + "vscode-languageserver-types": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.14.0.tgz", + "integrity": "sha512-lTmS6AlAlMHOvPQemVwo3CezxBp0sNB95KNPkqp3Nxd5VFEnuG1ByM0zlRWos0zjO3ZWtkvhal0COgiV1xIA4A==" + }, + "vscode-nls": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-4.1.2.tgz", + "integrity": "sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw==" + }, + "vscode-uri": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-1.0.8.tgz", + "integrity": "sha512-obtSWTlbJ+a+TFRYGaUumtVwb+InIUVI0Lu0VBUAPmj2cU5JutEXg3xUE0c2J5Tcy7h2DEKVJBFi+Y9ZSFzzPQ==" + } + } +} diff --git a/lsp-embedded-language-service/server/package.json b/lsp-embedded-language-service/server/package.json new file mode 100644 index 000000000..1f740c2e7 --- /dev/null +++ b/lsp-embedded-language-service/server/package.json @@ -0,0 +1,20 @@ +{ + "name": "lsp-sample-server", + "description": "Example implementation of a language server in node.", + "version": "1.0.0", + "author": "Microsoft Corporation", + "license": "MIT", + "engines": { + "node": "*" + }, + "repository": { + "type": "git", + "url": "https://github.com/Microsoft/vscode-extension-samples" + }, + "dependencies": { + "vscode-css-languageservice": "^4.1.1", + "vscode-html-languageservice": "^3.0.3", + "vscode-languageserver": "^5.2.1" + }, + "scripts": {} +} diff --git a/lsp-embedded-language-service/server/src/embeddedSupport.ts b/lsp-embedded-language-service/server/src/embeddedSupport.ts new file mode 100644 index 000000000..c3b8ffde4 --- /dev/null +++ b/lsp-embedded-language-service/server/src/embeddedSupport.ts @@ -0,0 +1,233 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { TextDocument, Position, LanguageService, TokenType, Range } from './languageModes'; + +export interface LanguageRange extends Range { + languageId: string | undefined; + attributeValue?: boolean; +} + +export interface HTMLDocumentRegions { + getEmbeddedDocument(languageId: string, ignoreAttributeValues?: boolean): TextDocument; + getLanguageRanges(range: Range): LanguageRange[]; + getLanguageAtPosition(position: Position): string | undefined; + getLanguagesInDocument(): string[]; + getImportedScripts(): string[]; +} + +export const CSS_STYLE_RULE = '__'; + +interface EmbeddedRegion { languageId: string | undefined; start: number; end: number; attributeValue?: boolean; } + + +export function getDocumentRegions(languageService: LanguageService, document: TextDocument): HTMLDocumentRegions { + let regions: EmbeddedRegion[] = []; + let scanner = languageService.createScanner(document.getText()); + let lastTagName: string = ''; + let lastAttributeName: string | null = null; + let languageIdFromType: string | undefined = undefined; + let importedScripts: string[] = []; + + let token = scanner.scan(); + while (token !== TokenType.EOS) { + switch (token) { + case TokenType.StartTag: + lastTagName = scanner.getTokenText(); + lastAttributeName = null; + languageIdFromType = 'javascript'; + break; + case TokenType.Styles: + regions.push({ languageId: 'css', start: scanner.getTokenOffset(), end: scanner.getTokenEnd() }); + break; + case TokenType.Script: + regions.push({ languageId: languageIdFromType, start: scanner.getTokenOffset(), end: scanner.getTokenEnd() }); + break; + case TokenType.AttributeName: + lastAttributeName = scanner.getTokenText(); + break; + case TokenType.AttributeValue: + if (lastAttributeName === 'src' && lastTagName.toLowerCase() === 'script') { + let value = scanner.getTokenText(); + if (value[0] === '\'' || value[0] === '"') { + value = value.substr(1, value.length - 1); + } + importedScripts.push(value); + } else if (lastAttributeName === 'type' && lastTagName.toLowerCase() === 'script') { + if (/["'](module|(text|application)\/(java|ecma)script|text\/babel)["']/.test(scanner.getTokenText())) { + languageIdFromType = 'javascript'; + } else if (/["']text\/typescript["']/.test(scanner.getTokenText())) { + languageIdFromType = 'typescript'; + } else { + languageIdFromType = undefined; + } + } else { + let attributeLanguageId = getAttributeLanguage(lastAttributeName!); + if (attributeLanguageId) { + let start = scanner.getTokenOffset(); + let end = scanner.getTokenEnd(); + let firstChar = document.getText()[start]; + if (firstChar === '\'' || firstChar === '"') { + start++; + end--; + } + regions.push({ languageId: attributeLanguageId, start, end, attributeValue: true }); + } + } + lastAttributeName = null; + break; + } + token = scanner.scan(); + } + return { + getLanguageRanges: (range: Range) => getLanguageRanges(document, regions, range), + getEmbeddedDocument: (languageId: string, ignoreAttributeValues: boolean) => getEmbeddedDocument(document, regions, languageId, ignoreAttributeValues), + getLanguageAtPosition: (position: Position) => getLanguageAtPosition(document, regions, position), + getLanguagesInDocument: () => getLanguagesInDocument(document, regions), + getImportedScripts: () => importedScripts + }; +} + + +function getLanguageRanges(document: TextDocument, regions: EmbeddedRegion[], range: Range): LanguageRange[] { + let result: LanguageRange[] = []; + let currentPos = range ? range.start : Position.create(0, 0); + let currentOffset = range ? document.offsetAt(range.start) : 0; + let endOffset = range ? document.offsetAt(range.end) : document.getText().length; + for (let region of regions) { + if (region.end > currentOffset && region.start < endOffset) { + let start = Math.max(region.start, currentOffset); + let startPos = document.positionAt(start); + if (currentOffset < region.start) { + result.push({ + start: currentPos, + end: startPos, + languageId: 'html' + }); + } + let end = Math.min(region.end, endOffset); + let endPos = document.positionAt(end); + if (end > region.start) { + result.push({ + start: startPos, + end: endPos, + languageId: region.languageId, + attributeValue: region.attributeValue + }); + } + currentOffset = end; + currentPos = endPos; + } + } + if (currentOffset < endOffset) { + let endPos = range ? range.end : document.positionAt(endOffset); + result.push({ + start: currentPos, + end: endPos, + languageId: 'html' + }); + } + return result; +} + +function getLanguagesInDocument(_document: TextDocument, regions: EmbeddedRegion[]): string[] { + let result = []; + for (let region of regions) { + if (region.languageId && result.indexOf(region.languageId) === -1) { + result.push(region.languageId); + if (result.length === 3) { + return result; + } + } + } + result.push('html'); + return result; +} + +function getLanguageAtPosition(document: TextDocument, regions: EmbeddedRegion[], position: Position): string | undefined { + let offset = document.offsetAt(position); + for (let region of regions) { + if (region.start <= offset) { + if (offset <= region.end) { + return region.languageId; + } + } else { + break; + } + } + return 'html'; +} + +function getEmbeddedDocument(document: TextDocument, contents: EmbeddedRegion[], languageId: string, ignoreAttributeValues: boolean): TextDocument { + let currentPos = 0; + let oldContent = document.getText(); + let result = ''; + let lastSuffix = ''; + for (let c of contents) { + if (c.languageId === languageId && (!ignoreAttributeValues || !c.attributeValue)) { + result = substituteWithWhitespace(result, currentPos, c.start, oldContent, lastSuffix, getPrefix(c)); + result += oldContent.substring(c.start, c.end); + currentPos = c.end; + lastSuffix = getSuffix(c); + } + } + result = substituteWithWhitespace(result, currentPos, oldContent.length, oldContent, lastSuffix, ''); + return TextDocument.create(document.uri, languageId, document.version, result); +} + +function getPrefix(c: EmbeddedRegion) { + if (c.attributeValue) { + switch (c.languageId) { + case 'css': return CSS_STYLE_RULE + '{'; + } + } + return ''; +} +function getSuffix(c: EmbeddedRegion) { + if (c.attributeValue) { + switch (c.languageId) { + case 'css': return '}'; + case 'javascript': return ';'; + } + } + return ''; +} + +function substituteWithWhitespace(result: string, start: number, end: number, oldContent: string, before: string, after: string) { + let accumulatedWS = 0; + result += before; + for (let i = start + before.length; i < end; i++) { + let ch = oldContent[i]; + if (ch === '\n' || ch === '\r') { + // only write new lines, skip the whitespace + accumulatedWS = 0; + result += ch; + } else { + accumulatedWS++; + } + } + result = append(result, ' ', accumulatedWS - after.length); + result += after; + return result; +} + +function append(result: string, str: string, n: number): string { + while (n > 0) { + if (n & 1) { + result += str; + } + n >>= 1; + str += str; + } + return result; +} + +function getAttributeLanguage(attributeName: string): string | null { + let match = attributeName.match(/^(style)$|^(on\w+)$/i); + if (!match) { + return null; + } + return match[1] ? 'css' : 'javascript'; +} diff --git a/lsp-embedded-language-service/server/src/languageModelCache.ts b/lsp-embedded-language-service/server/src/languageModelCache.ts new file mode 100644 index 000000000..06d331bca --- /dev/null +++ b/lsp-embedded-language-service/server/src/languageModelCache.ts @@ -0,0 +1,82 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { TextDocument } from 'vscode-html-languageservice'; + +export interface LanguageModelCache { + get(document: TextDocument): T; + onDocumentRemoved(document: TextDocument): void; + dispose(): void; +} + +export function getLanguageModelCache(maxEntries: number, cleanupIntervalTimeInSec: number, parse: (document: TextDocument) => T): LanguageModelCache { + let languageModels: { [uri: string]: { version: number, languageId: string, cTime: number, languageModel: T } } = {}; + let nModels = 0; + + let cleanupInterval: NodeJS.Timer | undefined = undefined; + if (cleanupIntervalTimeInSec > 0) { + cleanupInterval = setInterval(() => { + const cutoffTime = Date.now() - cleanupIntervalTimeInSec * 1000; + const uris = Object.keys(languageModels); + for (const uri of uris) { + const languageModelInfo = languageModels[uri]; + if (languageModelInfo.cTime < cutoffTime) { + delete languageModels[uri]; + nModels--; + } + } + }, cleanupIntervalTimeInSec * 1000); + } + + return { + get(document: TextDocument): T { + const version = document.version; + const languageId = document.languageId; + const languageModelInfo = languageModels[document.uri]; + if (languageModelInfo && languageModelInfo.version === version && languageModelInfo.languageId === languageId) { + languageModelInfo.cTime = Date.now(); + return languageModelInfo.languageModel; + } + const languageModel = parse(document); + languageModels[document.uri] = { languageModel, version, languageId, cTime: Date.now() }; + if (!languageModelInfo) { + nModels++; + } + + if (nModels === maxEntries) { + let oldestTime = Number.MAX_VALUE; + let oldestUri = null; + for (const uri in languageModels) { + const languageModelInfo = languageModels[uri]; + if (languageModelInfo.cTime < oldestTime) { + oldestUri = uri; + oldestTime = languageModelInfo.cTime; + } + } + if (oldestUri) { + delete languageModels[oldestUri]; + nModels--; + } + } + return languageModel; + + }, + onDocumentRemoved(document: TextDocument) { + const uri = document.uri; + if (languageModels[uri]) { + delete languageModels[uri]; + nModels--; + } + }, + dispose() { + if (typeof cleanupInterval !== 'undefined') { + clearInterval(cleanupInterval); + cleanupInterval = undefined; + languageModels = {}; + nModels = 0; + } + } + }; +} diff --git a/lsp-embedded-language-service/server/src/languageModes.ts b/lsp-embedded-language-service/server/src/languageModes.ts new file mode 100644 index 000000000..7cfd89890 --- /dev/null +++ b/lsp-embedded-language-service/server/src/languageModes.ts @@ -0,0 +1,122 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { getCSSLanguageService } from 'vscode-css-languageservice'; +import { + CompletionList, + Diagnostic, + getLanguageService as getHTMLLanguageService, + Position, + Range, + TextDocument +} from 'vscode-html-languageservice'; +import { getCSSMode } from './modes/cssMode'; +import { getDocumentRegions, HTMLDocumentRegions } from './embeddedSupport'; +import { getHTMLMode } from './modes/htmlMode'; +import { getLanguageModelCache, LanguageModelCache } from './languageModelCache'; + +export * from 'vscode-html-languageservice'; + +export interface LanguageMode { + getId(): string; + doValidation?: (document: TextDocument) => Diagnostic[]; + doComplete?: (document: TextDocument, position: Position) => CompletionList; + onDocumentRemoved(document: TextDocument): void; + dispose(): void; +} + +export interface LanguageModes { + getModeAtPosition(document: TextDocument, position: Position): LanguageMode | undefined; + getModesInRange(document: TextDocument, range: Range): LanguageModeRange[]; + getAllModes(): LanguageMode[]; + getAllModesInDocument(document: TextDocument): LanguageMode[]; + getMode(languageId: string): LanguageMode | undefined; + onDocumentRemoved(document: TextDocument): void; + dispose(): void; +} + +export interface LanguageModeRange extends Range { + mode: LanguageMode | undefined; + attributeValue?: boolean; +} + +export function getLanguageModes(): LanguageModes { + const htmlLanguageService = getHTMLLanguageService(); + const cssLanguageService = getCSSLanguageService(); + + let documentRegions = getLanguageModelCache(10, 60, document => + getDocumentRegions(htmlLanguageService, document) + ); + + let modelCaches: LanguageModelCache[] = []; + modelCaches.push(documentRegions); + + let modes = Object.create(null); + modes['html'] = getHTMLMode(htmlLanguageService); + modes['css'] = getCSSMode(cssLanguageService, documentRegions); + + return { + getModeAtPosition( + document: TextDocument, + position: Position + ): LanguageMode | undefined { + let languageId = documentRegions.get(document).getLanguageAtPosition(position); + if (languageId) { + return modes[languageId]; + } + return undefined; + }, + getModesInRange(document: TextDocument, range: Range): LanguageModeRange[] { + return documentRegions + .get(document) + .getLanguageRanges(range) + .map(r => { + return { + start: r.start, + end: r.end, + mode: r.languageId && modes[r.languageId], + attributeValue: r.attributeValue + }; + }); + }, + getAllModesInDocument(document: TextDocument): LanguageMode[] { + let result = []; + for (let languageId of documentRegions.get(document).getLanguagesInDocument()) { + let mode = modes[languageId]; + if (mode) { + result.push(mode); + } + } + return result; + }, + getAllModes(): LanguageMode[] { + let result = []; + for (let languageId in modes) { + let mode = modes[languageId]; + if (mode) { + result.push(mode); + } + } + return result; + }, + getMode(languageId: string): LanguageMode { + return modes[languageId]; + }, + onDocumentRemoved(document: TextDocument) { + modelCaches.forEach(mc => mc.onDocumentRemoved(document)); + for (let mode in modes) { + modes[mode].onDocumentRemoved(document); + } + }, + dispose(): void { + modelCaches.forEach(mc => mc.dispose()); + modelCaches = []; + for (let mode in modes) { + modes[mode].dispose(); + } + modes = {}; + } + }; +} diff --git a/lsp-embedded-language-service/server/src/modes/cssMode.ts b/lsp-embedded-language-service/server/src/modes/cssMode.ts new file mode 100644 index 000000000..8b7ec0015 --- /dev/null +++ b/lsp-embedded-language-service/server/src/modes/cssMode.ts @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { LanguageService as CSSLanguageService } from 'vscode-css-languageservice'; +import { HTMLDocumentRegions } from '../embeddedSupport'; +import { LanguageModelCache } from '../languageModelCache'; +import { LanguageMode, Position, TextDocument } from '../languageModes'; + +export function getCSSMode( + cssLanguageService: CSSLanguageService, + documentRegions: LanguageModelCache +): LanguageMode { + return { + getId() { + return 'css'; + }, + doValidation(document: TextDocument) { + // Get virtual CSS document, with all non-CSS code replaced with whitespace + const embedded = documentRegions.get(document).getEmbeddedDocument('css'); + const stylesheet = cssLanguageService.parseStylesheet(embedded); + return cssLanguageService.doValidation(embedded, stylesheet); + }, + doComplete(document: TextDocument, position: Position) { + // Get virtual CSS document, with all non-CSS code replaced with whitespace + const embedded = documentRegions.get(document).getEmbeddedDocument('css'); + const stylesheet = cssLanguageService.parseStylesheet(embedded); + return cssLanguageService.doComplete(embedded, position, stylesheet); + }, + onDocumentRemoved(_document: TextDocument) {}, + dispose() {} + }; +} diff --git a/lsp-embedded-language-service/server/src/modes/htmlMode.ts b/lsp-embedded-language-service/server/src/modes/htmlMode.ts new file mode 100644 index 000000000..9c4adb760 --- /dev/null +++ b/lsp-embedded-language-service/server/src/modes/htmlMode.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { + LanguageMode, + LanguageService as HTMLLanguageService, + Position, + TextDocument +} from '../languageModes'; + +export function getHTMLMode(htmlLanguageService: HTMLLanguageService): LanguageMode { + return { + getId() { + return 'html'; + }, + doComplete(document: TextDocument, position: Position) { + return htmlLanguageService.doComplete( + document, + position, + htmlLanguageService.parseHTMLDocument(document) + ); + }, + onDocumentRemoved(_document: TextDocument) {}, + dispose() {} + }; +} diff --git a/lsp-embedded-language-service/server/src/server.ts b/lsp-embedded-language-service/server/src/server.ts new file mode 100644 index 000000000..b274845ac --- /dev/null +++ b/lsp-embedded-language-service/server/src/server.ts @@ -0,0 +1,106 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * ------------------------------------------------------------------------------------------ */ + +import { + CompletionList, + createConnection, + Diagnostic, + InitializeParams, + ProposedFeatures, + TextDocument, + TextDocuments +} from 'vscode-languageserver'; +import { getLanguageModes, LanguageModes } from './languageModes'; + +// Create a connection for the server. The connection uses Node's IPC as a transport. +// Also include all preview / proposed LSP features. +let connection = createConnection(ProposedFeatures.all); + +// Create a simple text document manager. The text document manager +// supports full document sync only +let documents: TextDocuments = new TextDocuments(); + +let languageModes: LanguageModes; + +connection.onInitialize((_params: InitializeParams) => { + languageModes = getLanguageModes(); + + documents.onDidClose(e => { + languageModes.onDocumentRemoved(e.document); + }); + connection.onShutdown(() => { + languageModes.dispose(); + }); + + return { + capabilities: { + textDocumentSync: documents.syncKind, + // Tell the client that the server supports code completion + completionProvider: { + resolveProvider: false + } + } + }; +}); + +connection.onInitialized(() => {}); + +connection.onDidChangeConfiguration(_change => { + // Revalidate all open text documents + documents.all().forEach(validateTextDocument); +}); + +// The content of a text document has changed. This event is emitted +// when the text document first opened or when its content has changed. +documents.onDidChangeContent(change => { + validateTextDocument(change.document); +}); + +async function validateTextDocument(textDocument: TextDocument) { + try { + const version = textDocument.version; + const diagnostics: Diagnostic[] = []; + if (textDocument.languageId === 'html') { + const modes = languageModes.getAllModesInDocument(textDocument); + const latestTextDocument = documents.get(textDocument.uri); + if (latestTextDocument && latestTextDocument.version === version) { + // check no new version has come in after in after the async op + modes.forEach(mode => { + if (mode.doValidation) { + mode.doValidation(latestTextDocument).forEach(d => { + diagnostics.push(d); + }); + } + }); + connection.sendDiagnostics({ uri: latestTextDocument.uri, diagnostics }); + } + } + } catch (e) { + connection.console.error(`Error while validating ${textDocument.uri}`); + connection.console.error(e); + } +} + +connection.onCompletion(async (textDocumentPosition, token) => { + const document = documents.get(textDocumentPosition.textDocument.uri); + if (!document) { + return null; + } + + const mode = languageModes.getModeAtPosition(document, textDocumentPosition.position); + if (!mode || !mode.doComplete) { + return CompletionList.create(); + } + const doComplete = mode.doComplete!; + + return doComplete(document, textDocumentPosition.position); +}); + +// Make the text document manager listen on the connection +// for open, change and close text document events +documents.listen(connection); + +// Listen on the connection +connection.listen(); diff --git a/lsp-embedded-language-service/server/tsconfig.json b/lsp-embedded-language-service/server/tsconfig.json new file mode 100644 index 000000000..974842f3a --- /dev/null +++ b/lsp-embedded-language-service/server/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "strict": true, + "outDir": "out", + "rootDir": "src" + }, + "include": ["src"], + "exclude": ["node_modules", ".vscode-test"] +} diff --git a/lsp-embedded-language-service/tsconfig.json b/lsp-embedded-language-service/tsconfig.json new file mode 100644 index 000000000..a1a93135c --- /dev/null +++ b/lsp-embedded-language-service/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "outDir": "out", + "rootDir": "src", + "sourceMap": true + }, + "include": [ + "src" + ], + "exclude": [ + "node_modules", + ".vscode-test" + ], + "references": [ + { "path": "./client" }, + { "path": "./server" } + ] +} \ No newline at end of file diff --git a/lsp-embedded-language-service/tslint.json b/lsp-embedded-language-service/tslint.json new file mode 100644 index 000000000..0ab0ca6e8 --- /dev/null +++ b/lsp-embedded-language-service/tslint.json @@ -0,0 +1,6 @@ +{ + "rules": { + "indent": [true, "tabs"], + "semicolon": [true, "always"] + } +} \ No newline at end of file diff --git a/lsp-embedded-request-forwarding/.gitignore b/lsp-embedded-request-forwarding/.gitignore new file mode 100644 index 000000000..bf962da1f --- /dev/null +++ b/lsp-embedded-request-forwarding/.gitignore @@ -0,0 +1,4 @@ +out +node_modules +client/server +.vscode-test \ No newline at end of file diff --git a/lsp-embedded-request-forwarding/.vscode/launch.json b/lsp-embedded-request-forwarding/.vscode/launch.json new file mode 100644 index 000000000..99b82799a --- /dev/null +++ b/lsp-embedded-request-forwarding/.vscode/launch.json @@ -0,0 +1,44 @@ +// A launch configuration that compiles the extension and then opens it inside a new window +{ + "version": "0.2.0", + "configurations": [ + { + "type": "extensionHost", + "request": "launch", + "name": "Launch Client", + "runtimeExecutable": "${execPath}", + "args": ["--extensionDevelopmentPath=${workspaceRoot}"], + "outFiles": ["${workspaceRoot}/client/out/**/*.js"], + "preLaunchTask": { + "type": "npm", + "script": "watch" + } + }, + { + "type": "node", + "request": "attach", + "name": "Attach to Server", + "port": 6009, + "restart": true, + "outFiles": ["${workspaceRoot}/server/out/**/*.js"] + }, + { + "name": "Language Server E2E Test", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "--extensionDevelopmentPath=${workspaceRoot}", + "--extensionTestsPath=${workspaceRoot}/client/out/test", + "${workspaceRoot}/client/testFixture" + ], + "outFiles": ["${workspaceRoot}/client/out/test/**/*.js"] + } + ], + "compounds": [ + { + "name": "Client + Server", + "configurations": ["Launch Client", "Attach to Server"] + } + ] +} diff --git a/lsp-embedded-request-forwarding/.vscode/settings.json b/lsp-embedded-request-forwarding/.vscode/settings.json new file mode 100644 index 000000000..4a9bab7c8 --- /dev/null +++ b/lsp-embedded-request-forwarding/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "editor.insertSpaces": false, + "tslint.enable": true, + "typescript.tsc.autoDetect": "off", + "typescript.preferences.quoteStyle": "single" +} \ No newline at end of file diff --git a/lsp-embedded-request-forwarding/.vscode/tasks.json b/lsp-embedded-request-forwarding/.vscode/tasks.json new file mode 100644 index 000000000..5efd80488 --- /dev/null +++ b/lsp-embedded-request-forwarding/.vscode/tasks.json @@ -0,0 +1,33 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "compile", + "group": "build", + "presentation": { + "panel": "dedicated", + "reveal": "never" + }, + "problemMatcher": [ + "$tsc" + ] + }, + { + "type": "npm", + "script": "watch", + "isBackground": true, + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "panel": "dedicated", + "reveal": "never" + }, + "problemMatcher": [ + "$tsc-watch" + ] + } + ] +} \ No newline at end of file diff --git a/lsp-embedded-request-forwarding/.vscodeignore b/lsp-embedded-request-forwarding/.vscodeignore new file mode 100644 index 000000000..2ca7fc371 --- /dev/null +++ b/lsp-embedded-request-forwarding/.vscodeignore @@ -0,0 +1,14 @@ +.vscode/** +**/*.ts +**/*.map +.gitignore +**/tsconfig.json +**/tsconfig.base.json +contributing.md +.travis.yml +client/node_modules/** +!client/node_modules/vscode-jsonrpc/** +!client/node_modules/vscode-languageclient/** +!client/node_modules/vscode-languageserver-protocol/** +!client/node_modules/vscode-languageserver-types/** +!client/node_modules/semver/** diff --git a/lsp-embedded-request-forwarding/README.md b/lsp-embedded-request-forwarding/README.md new file mode 100644 index 000000000..9954390d6 --- /dev/null +++ b/lsp-embedded-request-forwarding/README.md @@ -0,0 +1,38 @@ +# LSP Example for Embedded Language using Language Service + +Heavily documented sample code for https://code.visualstudio.com/api/language-extensions/embedded-language-server-guide + +## Functionality + +This Language Server works for plain text file. It has the following language features: +- Completions +- Diagnostics regenerated on each file change or configuration change + +It also includes an End-to-End test. + +## Structure + +``` +. +├── client // Language Client +│ ├── src +│ │ ├── test // End to End tests for Language Client / Server +│ │ └── extension.ts // Language Client entry point +├── package.json // The extension manifest. +└── server // Language Server + └── src + └── server.ts // Language Server entry point +``` + +## Running the Sample + +- Run `npm install` in this folder. This installs all necessary npm modules in both the client and server folder +- Open VS Code on this folder. +- Press Ctrl+Shift+B to compile the client and server. +- Switch to the Debug viewlet. +- Select `Launch Client` from the drop down. +- Run the launch config. +- If you want to debug the server as well use the launch configuration `Attach to Server` +- In the [Extension Development Host] instance of VSCode, open a document in 'plain text' language mode. + - Type `j` or `t` to see `Javascript` and `TypeScript` completion. + - Enter text content such as `AAA aaa BBB`. The extension will emit diagnostics for all words in all-uppercase. diff --git a/lsp-embedded-request-forwarding/client/package-lock.json b/lsp-embedded-request-forwarding/client/package-lock.json new file mode 100644 index 000000000..588aca3be --- /dev/null +++ b/lsp-embedded-request-forwarding/client/package-lock.json @@ -0,0 +1,771 @@ +{ + "name": "lsp-sample-client", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "ajv": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "dev": true + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, + "requires": { + "es6-promise": "^4.0.3" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dev": true, + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "http-proxy-agent": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", + "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", + "dev": true, + "requires": { + "agent-base": "4", + "debug": "3.1.0" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-proxy-agent": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", + "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", + "dev": true, + "requires": { + "agent-base": "^4.1.0", + "debug": "^3.1.0" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", + "dev": true + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "dev": true, + "requires": { + "mime-db": "1.40.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "dev": true, + "requires": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" + }, + "dependencies": { + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "psl": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.2.0.tgz", + "integrity": "sha512-GEn74ZffufCmkDDLNcl3uuyF/aSD6exEyh1v/ZSdAomB82t6G9hzJVRx0jBmLDW+VfZqks3aScmMw9DszwUalA==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "querystringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", + "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==", + "dev": true + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", + "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "url-parse": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", + "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", + "dev": true, + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "vscode": { + "version": "1.1.35", + "resolved": "https://registry.npmjs.org/vscode/-/vscode-1.1.35.tgz", + "integrity": "sha512-xPnxzQU40LOS2yPyzWW+WKpTV6qA3z16TcgpZ9O38UWLA157Zz4GxUx5H7Gd07pxzw0GqvusbF4D+5GBgNxvEQ==", + "dev": true, + "requires": { + "glob": "^7.1.2", + "mocha": "^5.2.0", + "request": "^2.88.0", + "semver": "^5.4.1", + "source-map-support": "^0.5.0", + "url-parse": "^1.4.4", + "vscode-test": "^0.4.1" + } + }, + "vscode-html-languageservice": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-3.0.3.tgz", + "integrity": "sha512-U+upM3gHp3HaF3wXAnUduA6IDKcz6frWS/dTAju3cZVIyZwOLBBFElQVlLH0ycHyMzqUFrjvdv+kEyPAEWfQ/g==", + "requires": { + "vscode-languageserver-types": "^3.15.0-next.2", + "vscode-nls": "^4.1.1", + "vscode-uri": "^2.0.3" + }, + "dependencies": { + "vscode-languageserver-types": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz", + "integrity": "sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ==" + } + } + }, + "vscode-jsonrpc": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-4.0.0.tgz", + "integrity": "sha512-perEnXQdQOJMTDFNv+UF3h1Y0z4iSiaN9jIlb0OqIYgosPCZGYh/MCUlkFtV2668PL69lRDO32hmvL2yiidUYg==" + }, + "vscode-languageclient": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-5.2.1.tgz", + "integrity": "sha512-7jrS/9WnV0ruqPamN1nE7qCxn0phkH5LjSgSp9h6qoJGoeAKzwKz/PF6M+iGA/aklx4GLZg1prddhEPQtuXI1Q==", + "requires": { + "semver": "^5.5.0", + "vscode-languageserver-protocol": "3.14.1" + } + }, + "vscode-languageserver-protocol": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.14.1.tgz", + "integrity": "sha512-IL66BLb2g20uIKog5Y2dQ0IiigW0XKrvmWiOvc0yXw80z3tMEzEnHjaGAb3ENuU7MnQqgnYJ1Cl2l9RvNgDi4g==", + "requires": { + "vscode-jsonrpc": "^4.0.0", + "vscode-languageserver-types": "3.14.0" + } + }, + "vscode-languageserver-types": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.14.0.tgz", + "integrity": "sha512-lTmS6AlAlMHOvPQemVwo3CezxBp0sNB95KNPkqp3Nxd5VFEnuG1ByM0zlRWos0zjO3ZWtkvhal0COgiV1xIA4A==" + }, + "vscode-nls": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-4.1.2.tgz", + "integrity": "sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw==" + }, + "vscode-test": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/vscode-test/-/vscode-test-0.4.3.tgz", + "integrity": "sha512-EkMGqBSefZH2MgW65nY05rdRSko15uvzq4VAPM5jVmwYuFQKE7eikKXNJDRxL+OITXHB6pI+a3XqqD32Y3KC5w==", + "dev": true, + "requires": { + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.1" + } + }, + "vscode-uri": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-2.1.1.tgz", + "integrity": "sha512-eY9jmGoEnVf8VE8xr5znSah7Qt1P/xsCdErz+g8HYZtJ7bZqKH5E3d+6oVNm1AC/c6IHUDokbmVXKOi4qPAC9A==" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + } + } +} diff --git a/lsp-embedded-request-forwarding/client/package.json b/lsp-embedded-request-forwarding/client/package.json new file mode 100644 index 000000000..d448a6e9d --- /dev/null +++ b/lsp-embedded-request-forwarding/client/package.json @@ -0,0 +1,26 @@ +{ + "name": "lsp-sample-client", + "description": "VSCode part of a language server", + "author": "Microsoft Corporation", + "license": "MIT", + "version": "0.0.1", + "publisher": "vscode", + "repository": { + "type": "git", + "url": "https://github.com/Microsoft/vscode-extension-samples" + }, + "engines": { + "vscode": "^1.33.0" + }, + "scripts": { + "update-vscode": "vscode-install", + "postinstall": "vscode-install" + }, + "dependencies": { + "vscode-html-languageservice": "^3.0.3", + "vscode-languageclient": "^5.2.1" + }, + "devDependencies": { + "vscode": "^1.1.35" + } +} diff --git a/lsp-embedded-request-forwarding/client/src/embeddedSupport.ts b/lsp-embedded-request-forwarding/client/src/embeddedSupport.ts new file mode 100644 index 000000000..a71625e1c --- /dev/null +++ b/lsp-embedded-request-forwarding/client/src/embeddedSupport.ts @@ -0,0 +1,394 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { TextDocument, Position, Range } from 'vscode-languageclient'; +import { LanguageService, TokenType } from 'vscode-html-languageservice'; + +export interface LanguageRange extends Range { + languageId: string | undefined; + attributeValue?: boolean; +} + +export interface HTMLDocumentRegions { + getEmbeddedDocument(languageId: string, ignoreAttributeValues?: boolean): TextDocument; + getLanguageRanges(range: Range): LanguageRange[]; + getLanguageAtPosition(position: Position): string | undefined; + getLanguagesInDocument(): string[]; + getImportedScripts(): string[]; +} + +export const CSS_STYLE_RULE = '__'; + +interface EmbeddedRegion { + languageId: string | undefined; + start: number; + end: number; + attributeValue?: boolean; +} + +export function getCSSVirtualContent( + languageService: LanguageService, + documentText: string +): string { + let regions: EmbeddedRegion[] = []; + let scanner = languageService.createScanner(documentText); + let lastTagName: string = ''; + let lastAttributeName: string | null = null; + let languageIdFromType: string | undefined = undefined; + let importedScripts: string[] = []; + + let token = scanner.scan(); + while (token !== TokenType.EOS) { + switch (token) { + case TokenType.StartTag: + lastTagName = scanner.getTokenText(); + lastAttributeName = null; + languageIdFromType = 'javascript'; + break; + case TokenType.Styles: + regions.push({ + languageId: 'css', + start: scanner.getTokenOffset(), + end: scanner.getTokenEnd() + }); + break; + case TokenType.Script: + regions.push({ + languageId: languageIdFromType, + start: scanner.getTokenOffset(), + end: scanner.getTokenEnd() + }); + break; + case TokenType.AttributeName: + lastAttributeName = scanner.getTokenText(); + break; + case TokenType.AttributeValue: + if (lastAttributeName === 'src' && lastTagName.toLowerCase() === 'script') { + let value = scanner.getTokenText(); + if (value[0] === "'" || value[0] === '"') { + value = value.substr(1, value.length - 1); + } + importedScripts.push(value); + } else if (lastAttributeName === 'type' && lastTagName.toLowerCase() === 'script') { + if ( + /["'](module|(text|application)\/(java|ecma)script|text\/babel)["']/.test( + scanner.getTokenText() + ) + ) { + languageIdFromType = 'javascript'; + } else if (/["']text\/typescript["']/.test(scanner.getTokenText())) { + languageIdFromType = 'typescript'; + } else { + languageIdFromType = undefined; + } + } else { + let attributeLanguageId = getAttributeLanguage(lastAttributeName!); + if (attributeLanguageId) { + let start = scanner.getTokenOffset(); + let end = scanner.getTokenEnd(); + let firstChar = documentText[start]; + if (firstChar === "'" || firstChar === '"') { + start++; + end--; + } + regions.push({ + languageId: attributeLanguageId, + start, + end, + attributeValue: true + }); + } + } + lastAttributeName = null; + break; + } + token = scanner.scan(); + } + + let content = documentText + .split('\n') + .map(line => { + return ' '.repeat(line.length); + }).join('\n'); + + regions.forEach(r => { + if (r.languageId === 'css') { + content = content.slice(0, r.start) + documentText.slice(r.start, r.end) + content.slice(r.end); + } + }); + + return content; +} + + +export function getDocumentRegions( + languageService: LanguageService, + document: TextDocument +): HTMLDocumentRegions { + let regions: EmbeddedRegion[] = []; + let scanner = languageService.createScanner(document.getText()); + let lastTagName: string = ''; + let lastAttributeName: string | null = null; + let languageIdFromType: string | undefined = undefined; + let importedScripts: string[] = []; + + let token = scanner.scan(); + while (token !== TokenType.EOS) { + switch (token) { + case TokenType.StartTag: + lastTagName = scanner.getTokenText(); + lastAttributeName = null; + languageIdFromType = 'javascript'; + break; + case TokenType.Styles: + regions.push({ + languageId: 'css', + start: scanner.getTokenOffset(), + end: scanner.getTokenEnd() + }); + break; + case TokenType.Script: + regions.push({ + languageId: languageIdFromType, + start: scanner.getTokenOffset(), + end: scanner.getTokenEnd() + }); + break; + case TokenType.AttributeName: + lastAttributeName = scanner.getTokenText(); + break; + case TokenType.AttributeValue: + if (lastAttributeName === 'src' && lastTagName.toLowerCase() === 'script') { + let value = scanner.getTokenText(); + if (value[0] === "'" || value[0] === '"') { + value = value.substr(1, value.length - 1); + } + importedScripts.push(value); + } else if (lastAttributeName === 'type' && lastTagName.toLowerCase() === 'script') { + if ( + /["'](module|(text|application)\/(java|ecma)script|text\/babel)["']/.test( + scanner.getTokenText() + ) + ) { + languageIdFromType = 'javascript'; + } else if (/["']text\/typescript["']/.test(scanner.getTokenText())) { + languageIdFromType = 'typescript'; + } else { + languageIdFromType = undefined; + } + } else { + let attributeLanguageId = getAttributeLanguage(lastAttributeName!); + if (attributeLanguageId) { + let start = scanner.getTokenOffset(); + let end = scanner.getTokenEnd(); + let firstChar = document.getText()[start]; + if (firstChar === "'" || firstChar === '"') { + start++; + end--; + } + regions.push({ + languageId: attributeLanguageId, + start, + end, + attributeValue: true + }); + } + } + lastAttributeName = null; + break; + } + token = scanner.scan(); + } + return { + getLanguageRanges: (range: Range) => getLanguageRanges(document, regions, range), + getEmbeddedDocument: (languageId: string, ignoreAttributeValues: boolean) => + getEmbeddedDocument(document, regions, languageId, ignoreAttributeValues), + getLanguageAtPosition: (position: Position) => + getLanguageAtPosition(document, regions, position), + getLanguagesInDocument: () => getLanguagesInDocument(document, regions), + getImportedScripts: () => importedScripts + }; +} + +function getLanguageRanges( + document: TextDocument, + regions: EmbeddedRegion[], + range: Range +): LanguageRange[] { + let result: LanguageRange[] = []; + let currentPos = range ? range.start : Position.create(0, 0); + let currentOffset = range ? document.offsetAt(range.start) : 0; + let endOffset = range ? document.offsetAt(range.end) : document.getText().length; + for (let region of regions) { + if (region.end > currentOffset && region.start < endOffset) { + let start = Math.max(region.start, currentOffset); + let startPos = document.positionAt(start); + if (currentOffset < region.start) { + result.push({ + start: currentPos, + end: startPos, + languageId: 'html' + }); + } + let end = Math.min(region.end, endOffset); + let endPos = document.positionAt(end); + if (end > region.start) { + result.push({ + start: startPos, + end: endPos, + languageId: region.languageId, + attributeValue: region.attributeValue + }); + } + currentOffset = end; + currentPos = endPos; + } + } + if (currentOffset < endOffset) { + let endPos = range ? range.end : document.positionAt(endOffset); + result.push({ + start: currentPos, + end: endPos, + languageId: 'html' + }); + } + return result; +} + +function getLanguagesInDocument( + _document: TextDocument, + regions: EmbeddedRegion[] +): string[] { + let result = []; + for (let region of regions) { + if (region.languageId && result.indexOf(region.languageId) === -1) { + result.push(region.languageId); + if (result.length === 3) { + return result; + } + } + } + result.push('html'); + return result; +} + +function getLanguageAtPosition( + document: TextDocument, + regions: EmbeddedRegion[], + position: Position +): string | undefined { + let offset = document.offsetAt(position); + for (let region of regions) { + if (region.start <= offset) { + if (offset <= region.end) { + return region.languageId; + } + } else { + break; + } + } + return 'html'; +} + +function getEmbeddedDocument( + document: TextDocument, + contents: EmbeddedRegion[], + languageId: string, + ignoreAttributeValues: boolean +): TextDocument { + let currentPos = 0; + let oldContent = document.getText(); + let result = ''; + let lastSuffix = ''; + for (let c of contents) { + if (c.languageId === languageId && (!ignoreAttributeValues || !c.attributeValue)) { + result = substituteWithWhitespace( + result, + currentPos, + c.start, + oldContent, + lastSuffix, + getPrefix(c) + ); + result += oldContent.substring(c.start, c.end); + currentPos = c.end; + lastSuffix = getSuffix(c); + } + } + result = substituteWithWhitespace( + result, + currentPos, + oldContent.length, + oldContent, + lastSuffix, + '' + ); + return TextDocument.create(document.uri, languageId, document.version, result); +} + +function getPrefix(c: EmbeddedRegion) { + if (c.attributeValue) { + switch (c.languageId) { + case 'css': + return CSS_STYLE_RULE + '{'; + } + } + return ''; +} +function getSuffix(c: EmbeddedRegion) { + if (c.attributeValue) { + switch (c.languageId) { + case 'css': + return '}'; + case 'javascript': + return ';'; + } + } + return ''; +} + +function substituteWithWhitespace( + result: string, + start: number, + end: number, + oldContent: string, + before: string, + after: string +) { + let accumulatedWS = 0; + result += before; + for (let i = start + before.length; i < end; i++) { + let ch = oldContent[i]; + if (ch === '\n' || ch === '\r') { + // only write new lines, skip the whitespace + accumulatedWS = 0; + result += ch; + } else { + accumulatedWS++; + } + } + result = append(result, ' ', accumulatedWS - after.length); + result += after; + return result; +} + +function append(result: string, str: string, n: number): string { + while (n > 0) { + if (n & 1) { + result += str; + } + n >>= 1; + str += str; + } + return result; +} + +function getAttributeLanguage(attributeName: string): string | null { + let match = attributeName.match(/^(style)$|^(on\w+)$/i); + if (!match) { + return null; + } + return match[1] ? 'css' : 'javascript'; +} diff --git a/lsp-embedded-request-forwarding/client/src/extension.ts b/lsp-embedded-request-forwarding/client/src/extension.ts new file mode 100644 index 000000000..140890119 --- /dev/null +++ b/lsp-embedded-request-forwarding/client/src/extension.ts @@ -0,0 +1,81 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * ------------------------------------------------------------------------------------------ */ + +import * as path from 'path'; +import { commands, CompletionList, ExtensionContext, Uri, workspace } from 'vscode'; +import { getLanguageService } from 'vscode-html-languageservice'; +import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient'; +import { getCSSVirtualContent } from './embeddedSupport'; + +let client: LanguageClient; + +const htmlLanguageService = getLanguageService(); + +export function activate(context: ExtensionContext) { + // The server is implemented in node + let serverModule = context.asAbsolutePath(path.join('server', 'out', 'server.js')); + // The debug options for the server + // --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging + let debugOptions = { execArgv: ['--nolazy', '--inspect=6009'] }; + + // If the extension is launched in debug mode then the debug server options are used + // Otherwise the run options are used + let serverOptions: ServerOptions = { + run: { module: serverModule, transport: TransportKind.ipc }, + debug: { + module: serverModule, + transport: TransportKind.ipc, + options: debugOptions + } + }; + + const virtualDocumentContents = new Map(); + + workspace.registerTextDocumentContentProvider('embedded-content', { + provideTextDocumentContent: uri => { + const originalUri = uri.fsPath.slice(1).slice(0, -4); + return virtualDocumentContents.get(originalUri); + } + }); + + let clientOptions: LanguageClientOptions = { + documentSelector: [{ scheme: 'file', language: 'html' }], + middleware: { + provideCompletionItem: async (document, position, context, token, next) => { + const originalUri = document.uri.toString(); + virtualDocumentContents.set(originalUri, getCSSVirtualContent(htmlLanguageService, document.getText())); + + const vdocUriString = `embedded-content://css/${encodeURIComponent( + originalUri + )}.css`; + const vdocUri = Uri.parse(vdocUriString); + return await commands.executeCommand( + 'vscode.executeCompletionItemProvider', + vdocUri, + position, + context.triggerCharacter + ); + } + } + }; + + // Create the language client and start the client. + client = new LanguageClient( + 'languageServerExample', + 'Language Server Example', + serverOptions, + clientOptions + ); + + // Start the client. This will also launch the server + client.start(); +} + +export function deactivate(): Thenable | undefined { + if (!client) { + return undefined; + } + return client.stop(); +} diff --git a/lsp-embedded-request-forwarding/client/tsconfig.json b/lsp-embedded-request-forwarding/client/tsconfig.json new file mode 100644 index 000000000..9e5ff6a24 --- /dev/null +++ b/lsp-embedded-request-forwarding/client/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "outDir": "out", + "rootDir": "src", + "sourceMap": true + }, + "include": ["src"], + "exclude": ["node_modules", ".vscode-test"] +} diff --git a/lsp-embedded-request-forwarding/package-lock.json b/lsp-embedded-request-forwarding/package-lock.json new file mode 100644 index 000000000..1b9c52415 --- /dev/null +++ b/lsp-embedded-request-forwarding/package-lock.json @@ -0,0 +1,323 @@ +{ + "name": "lsp-embedded-language-service-sample", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@types/mocha": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", + "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", + "dev": true + }, + "@types/node": { + "version": "12.11.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.11.7.tgz", + "integrity": "sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "commander": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "resolve": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.1.tgz", + "integrity": "sha512-KuIe4mf++td/eFb6wkaPbMDnP6kObCaEtIDuHOUED6MNUo4K670KZUHuuvYPZDxNF0WVLw49n06M2m2dXphEzA==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "dev": true + }, + "tslint": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.16.0.tgz", + "integrity": "sha512-UxG2yNxJ5pgGwmMzPMYh/CCnCnh0HfPgtlVRDs1ykZklufFBL1ZoTlWFRz2NQjcoEiDoRp+JyT0lhBbbH/obyA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^3.2.0", + "glob": "^7.1.1", + "js-yaml": "^3.13.0", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.29.0" + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "typescript": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.4.tgz", + "integrity": "sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + } + } +} diff --git a/lsp-embedded-request-forwarding/package.json b/lsp-embedded-request-forwarding/package.json new file mode 100644 index 000000000..599c35d5c --- /dev/null +++ b/lsp-embedded-request-forwarding/package.json @@ -0,0 +1,35 @@ +{ + "name": "lsp-embedded-language-service-sample", + "description": "A language server example using language services to support embedded languages", + "author": "Microsoft Corporation", + "license": "MIT", + "version": "1.0.0", + "repository": { + "type": "git", + "url": "https://github.com/Microsoft/vscode-extension-samples" + }, + "publisher": "vscode-samples", + "categories": [], + "keywords": [], + "engines": { + "vscode": "^1.33.0" + }, + "activationEvents": [ + "onLanguage:html" + ], + "main": "./client/out/extension", + "contributes": {}, + "scripts": { + "vscode:prepublish": "cd client && npm run update-vscode && cd .. && npm run compile", + "compile": "tsc -b", + "watch": "tsc -b -w", + "postinstall": "cd client && npm install && cd ../server && npm install && cd ..", + "test": "sh ./scripts/e2e.sh" + }, + "devDependencies": { + "@types/mocha": "^5.2.7", + "@types/node": "^12.11.7", + "tslint": "^5.16.0", + "typescript": "^3.6.4" + } +} diff --git a/lsp-embedded-request-forwarding/server/package-lock.json b/lsp-embedded-request-forwarding/server/package-lock.json new file mode 100644 index 000000000..a98f7dd82 --- /dev/null +++ b/lsp-embedded-request-forwarding/server/package-lock.json @@ -0,0 +1,68 @@ +{ + "name": "lsp-sample-server", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "vscode-html-languageservice": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-3.0.3.tgz", + "integrity": "sha512-U+upM3gHp3HaF3wXAnUduA6IDKcz6frWS/dTAju3cZVIyZwOLBBFElQVlLH0ycHyMzqUFrjvdv+kEyPAEWfQ/g==", + "requires": { + "vscode-languageserver-types": "^3.15.0-next.2", + "vscode-nls": "^4.1.1", + "vscode-uri": "^2.0.3" + }, + "dependencies": { + "vscode-languageserver-types": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz", + "integrity": "sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ==" + }, + "vscode-uri": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-2.1.1.tgz", + "integrity": "sha512-eY9jmGoEnVf8VE8xr5znSah7Qt1P/xsCdErz+g8HYZtJ7bZqKH5E3d+6oVNm1AC/c6IHUDokbmVXKOi4qPAC9A==" + } + } + }, + "vscode-jsonrpc": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-4.0.0.tgz", + "integrity": "sha512-perEnXQdQOJMTDFNv+UF3h1Y0z4iSiaN9jIlb0OqIYgosPCZGYh/MCUlkFtV2668PL69lRDO32hmvL2yiidUYg==" + }, + "vscode-languageserver": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-5.2.1.tgz", + "integrity": "sha512-GuayqdKZqAwwaCUjDvMTAVRPJOp/SLON3mJ07eGsx/Iq9HjRymhKWztX41rISqDKhHVVyFM+IywICyZDla6U3A==", + "requires": { + "vscode-languageserver-protocol": "3.14.1", + "vscode-uri": "^1.0.6" + } + }, + "vscode-languageserver-protocol": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.14.1.tgz", + "integrity": "sha512-IL66BLb2g20uIKog5Y2dQ0IiigW0XKrvmWiOvc0yXw80z3tMEzEnHjaGAb3ENuU7MnQqgnYJ1Cl2l9RvNgDi4g==", + "requires": { + "vscode-jsonrpc": "^4.0.0", + "vscode-languageserver-types": "3.14.0" + } + }, + "vscode-languageserver-types": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.14.0.tgz", + "integrity": "sha512-lTmS6AlAlMHOvPQemVwo3CezxBp0sNB95KNPkqp3Nxd5VFEnuG1ByM0zlRWos0zjO3ZWtkvhal0COgiV1xIA4A==" + }, + "vscode-nls": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-4.1.2.tgz", + "integrity": "sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw==" + }, + "vscode-uri": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-1.0.8.tgz", + "integrity": "sha512-obtSWTlbJ+a+TFRYGaUumtVwb+InIUVI0Lu0VBUAPmj2cU5JutEXg3xUE0c2J5Tcy7h2DEKVJBFi+Y9ZSFzzPQ==" + } + } +} diff --git a/lsp-embedded-request-forwarding/server/package.json b/lsp-embedded-request-forwarding/server/package.json new file mode 100644 index 000000000..f9e0bfa08 --- /dev/null +++ b/lsp-embedded-request-forwarding/server/package.json @@ -0,0 +1,19 @@ +{ + "name": "lsp-sample-server", + "description": "Example implementation of a language server in node.", + "version": "1.0.0", + "author": "Microsoft Corporation", + "license": "MIT", + "engines": { + "node": "*" + }, + "repository": { + "type": "git", + "url": "https://github.com/Microsoft/vscode-extension-samples" + }, + "dependencies": { + "vscode-html-languageservice": "^3.0.3", + "vscode-languageserver": "^5.2.1" + }, + "scripts": {} +} diff --git a/lsp-embedded-request-forwarding/server/src/embeddedSupport.ts b/lsp-embedded-request-forwarding/server/src/embeddedSupport.ts new file mode 100644 index 000000000..c3b8ffde4 --- /dev/null +++ b/lsp-embedded-request-forwarding/server/src/embeddedSupport.ts @@ -0,0 +1,233 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { TextDocument, Position, LanguageService, TokenType, Range } from './languageModes'; + +export interface LanguageRange extends Range { + languageId: string | undefined; + attributeValue?: boolean; +} + +export interface HTMLDocumentRegions { + getEmbeddedDocument(languageId: string, ignoreAttributeValues?: boolean): TextDocument; + getLanguageRanges(range: Range): LanguageRange[]; + getLanguageAtPosition(position: Position): string | undefined; + getLanguagesInDocument(): string[]; + getImportedScripts(): string[]; +} + +export const CSS_STYLE_RULE = '__'; + +interface EmbeddedRegion { languageId: string | undefined; start: number; end: number; attributeValue?: boolean; } + + +export function getDocumentRegions(languageService: LanguageService, document: TextDocument): HTMLDocumentRegions { + let regions: EmbeddedRegion[] = []; + let scanner = languageService.createScanner(document.getText()); + let lastTagName: string = ''; + let lastAttributeName: string | null = null; + let languageIdFromType: string | undefined = undefined; + let importedScripts: string[] = []; + + let token = scanner.scan(); + while (token !== TokenType.EOS) { + switch (token) { + case TokenType.StartTag: + lastTagName = scanner.getTokenText(); + lastAttributeName = null; + languageIdFromType = 'javascript'; + break; + case TokenType.Styles: + regions.push({ languageId: 'css', start: scanner.getTokenOffset(), end: scanner.getTokenEnd() }); + break; + case TokenType.Script: + regions.push({ languageId: languageIdFromType, start: scanner.getTokenOffset(), end: scanner.getTokenEnd() }); + break; + case TokenType.AttributeName: + lastAttributeName = scanner.getTokenText(); + break; + case TokenType.AttributeValue: + if (lastAttributeName === 'src' && lastTagName.toLowerCase() === 'script') { + let value = scanner.getTokenText(); + if (value[0] === '\'' || value[0] === '"') { + value = value.substr(1, value.length - 1); + } + importedScripts.push(value); + } else if (lastAttributeName === 'type' && lastTagName.toLowerCase() === 'script') { + if (/["'](module|(text|application)\/(java|ecma)script|text\/babel)["']/.test(scanner.getTokenText())) { + languageIdFromType = 'javascript'; + } else if (/["']text\/typescript["']/.test(scanner.getTokenText())) { + languageIdFromType = 'typescript'; + } else { + languageIdFromType = undefined; + } + } else { + let attributeLanguageId = getAttributeLanguage(lastAttributeName!); + if (attributeLanguageId) { + let start = scanner.getTokenOffset(); + let end = scanner.getTokenEnd(); + let firstChar = document.getText()[start]; + if (firstChar === '\'' || firstChar === '"') { + start++; + end--; + } + regions.push({ languageId: attributeLanguageId, start, end, attributeValue: true }); + } + } + lastAttributeName = null; + break; + } + token = scanner.scan(); + } + return { + getLanguageRanges: (range: Range) => getLanguageRanges(document, regions, range), + getEmbeddedDocument: (languageId: string, ignoreAttributeValues: boolean) => getEmbeddedDocument(document, regions, languageId, ignoreAttributeValues), + getLanguageAtPosition: (position: Position) => getLanguageAtPosition(document, regions, position), + getLanguagesInDocument: () => getLanguagesInDocument(document, regions), + getImportedScripts: () => importedScripts + }; +} + + +function getLanguageRanges(document: TextDocument, regions: EmbeddedRegion[], range: Range): LanguageRange[] { + let result: LanguageRange[] = []; + let currentPos = range ? range.start : Position.create(0, 0); + let currentOffset = range ? document.offsetAt(range.start) : 0; + let endOffset = range ? document.offsetAt(range.end) : document.getText().length; + for (let region of regions) { + if (region.end > currentOffset && region.start < endOffset) { + let start = Math.max(region.start, currentOffset); + let startPos = document.positionAt(start); + if (currentOffset < region.start) { + result.push({ + start: currentPos, + end: startPos, + languageId: 'html' + }); + } + let end = Math.min(region.end, endOffset); + let endPos = document.positionAt(end); + if (end > region.start) { + result.push({ + start: startPos, + end: endPos, + languageId: region.languageId, + attributeValue: region.attributeValue + }); + } + currentOffset = end; + currentPos = endPos; + } + } + if (currentOffset < endOffset) { + let endPos = range ? range.end : document.positionAt(endOffset); + result.push({ + start: currentPos, + end: endPos, + languageId: 'html' + }); + } + return result; +} + +function getLanguagesInDocument(_document: TextDocument, regions: EmbeddedRegion[]): string[] { + let result = []; + for (let region of regions) { + if (region.languageId && result.indexOf(region.languageId) === -1) { + result.push(region.languageId); + if (result.length === 3) { + return result; + } + } + } + result.push('html'); + return result; +} + +function getLanguageAtPosition(document: TextDocument, regions: EmbeddedRegion[], position: Position): string | undefined { + let offset = document.offsetAt(position); + for (let region of regions) { + if (region.start <= offset) { + if (offset <= region.end) { + return region.languageId; + } + } else { + break; + } + } + return 'html'; +} + +function getEmbeddedDocument(document: TextDocument, contents: EmbeddedRegion[], languageId: string, ignoreAttributeValues: boolean): TextDocument { + let currentPos = 0; + let oldContent = document.getText(); + let result = ''; + let lastSuffix = ''; + for (let c of contents) { + if (c.languageId === languageId && (!ignoreAttributeValues || !c.attributeValue)) { + result = substituteWithWhitespace(result, currentPos, c.start, oldContent, lastSuffix, getPrefix(c)); + result += oldContent.substring(c.start, c.end); + currentPos = c.end; + lastSuffix = getSuffix(c); + } + } + result = substituteWithWhitespace(result, currentPos, oldContent.length, oldContent, lastSuffix, ''); + return TextDocument.create(document.uri, languageId, document.version, result); +} + +function getPrefix(c: EmbeddedRegion) { + if (c.attributeValue) { + switch (c.languageId) { + case 'css': return CSS_STYLE_RULE + '{'; + } + } + return ''; +} +function getSuffix(c: EmbeddedRegion) { + if (c.attributeValue) { + switch (c.languageId) { + case 'css': return '}'; + case 'javascript': return ';'; + } + } + return ''; +} + +function substituteWithWhitespace(result: string, start: number, end: number, oldContent: string, before: string, after: string) { + let accumulatedWS = 0; + result += before; + for (let i = start + before.length; i < end; i++) { + let ch = oldContent[i]; + if (ch === '\n' || ch === '\r') { + // only write new lines, skip the whitespace + accumulatedWS = 0; + result += ch; + } else { + accumulatedWS++; + } + } + result = append(result, ' ', accumulatedWS - after.length); + result += after; + return result; +} + +function append(result: string, str: string, n: number): string { + while (n > 0) { + if (n & 1) { + result += str; + } + n >>= 1; + str += str; + } + return result; +} + +function getAttributeLanguage(attributeName: string): string | null { + let match = attributeName.match(/^(style)$|^(on\w+)$/i); + if (!match) { + return null; + } + return match[1] ? 'css' : 'javascript'; +} diff --git a/lsp-embedded-request-forwarding/server/src/languageModelCache.ts b/lsp-embedded-request-forwarding/server/src/languageModelCache.ts new file mode 100644 index 000000000..06d331bca --- /dev/null +++ b/lsp-embedded-request-forwarding/server/src/languageModelCache.ts @@ -0,0 +1,82 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { TextDocument } from 'vscode-html-languageservice'; + +export interface LanguageModelCache { + get(document: TextDocument): T; + onDocumentRemoved(document: TextDocument): void; + dispose(): void; +} + +export function getLanguageModelCache(maxEntries: number, cleanupIntervalTimeInSec: number, parse: (document: TextDocument) => T): LanguageModelCache { + let languageModels: { [uri: string]: { version: number, languageId: string, cTime: number, languageModel: T } } = {}; + let nModels = 0; + + let cleanupInterval: NodeJS.Timer | undefined = undefined; + if (cleanupIntervalTimeInSec > 0) { + cleanupInterval = setInterval(() => { + const cutoffTime = Date.now() - cleanupIntervalTimeInSec * 1000; + const uris = Object.keys(languageModels); + for (const uri of uris) { + const languageModelInfo = languageModels[uri]; + if (languageModelInfo.cTime < cutoffTime) { + delete languageModels[uri]; + nModels--; + } + } + }, cleanupIntervalTimeInSec * 1000); + } + + return { + get(document: TextDocument): T { + const version = document.version; + const languageId = document.languageId; + const languageModelInfo = languageModels[document.uri]; + if (languageModelInfo && languageModelInfo.version === version && languageModelInfo.languageId === languageId) { + languageModelInfo.cTime = Date.now(); + return languageModelInfo.languageModel; + } + const languageModel = parse(document); + languageModels[document.uri] = { languageModel, version, languageId, cTime: Date.now() }; + if (!languageModelInfo) { + nModels++; + } + + if (nModels === maxEntries) { + let oldestTime = Number.MAX_VALUE; + let oldestUri = null; + for (const uri in languageModels) { + const languageModelInfo = languageModels[uri]; + if (languageModelInfo.cTime < oldestTime) { + oldestUri = uri; + oldestTime = languageModelInfo.cTime; + } + } + if (oldestUri) { + delete languageModels[oldestUri]; + nModels--; + } + } + return languageModel; + + }, + onDocumentRemoved(document: TextDocument) { + const uri = document.uri; + if (languageModels[uri]) { + delete languageModels[uri]; + nModels--; + } + }, + dispose() { + if (typeof cleanupInterval !== 'undefined') { + clearInterval(cleanupInterval); + cleanupInterval = undefined; + languageModels = {}; + nModels = 0; + } + } + }; +} diff --git a/lsp-embedded-request-forwarding/server/src/languageModes.ts b/lsp-embedded-request-forwarding/server/src/languageModes.ts new file mode 100644 index 000000000..be54c0ae6 --- /dev/null +++ b/lsp-embedded-request-forwarding/server/src/languageModes.ts @@ -0,0 +1,118 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { + CompletionList, + Diagnostic, + getLanguageService as getHTMLLanguageService, + Position, + Range, + TextDocument +} from 'vscode-html-languageservice'; +import { getDocumentRegions, HTMLDocumentRegions } from './embeddedSupport'; +import { getHTMLMode } from './modes/htmlMode'; +import { getLanguageModelCache, LanguageModelCache } from './languageModelCache'; + +export * from 'vscode-html-languageservice'; + +export interface LanguageMode { + getId(): string; + doValidation?: (document: TextDocument) => Diagnostic[]; + doComplete?: (document: TextDocument, position: Position) => CompletionList; + onDocumentRemoved(document: TextDocument): void; + dispose(): void; +} + +export interface LanguageModes { + getModeAtPosition(document: TextDocument, position: Position): LanguageMode | undefined; + getModesInRange(document: TextDocument, range: Range): LanguageModeRange[]; + getAllModes(): LanguageMode[]; + getAllModesInDocument(document: TextDocument): LanguageMode[]; + getMode(languageId: string): LanguageMode | undefined; + onDocumentRemoved(document: TextDocument): void; + dispose(): void; +} + +export interface LanguageModeRange extends Range { + mode: LanguageMode | undefined; + attributeValue?: boolean; +} + +export function getLanguageModes(): LanguageModes { + const htmlLanguageService = getHTMLLanguageService(); + + let documentRegions = getLanguageModelCache(10, 60, document => + getDocumentRegions(htmlLanguageService, document) + ); + + let modelCaches: LanguageModelCache[] = []; + modelCaches.push(documentRegions); + + let modes = Object.create(null); + modes['html'] = getHTMLMode(htmlLanguageService); + + return { + getModeAtPosition( + document: TextDocument, + position: Position + ): LanguageMode | undefined { + let languageId = documentRegions.get(document).getLanguageAtPosition(position); + if (languageId) { + return modes[languageId]; + } + return undefined; + }, + getModesInRange(document: TextDocument, range: Range): LanguageModeRange[] { + return documentRegions + .get(document) + .getLanguageRanges(range) + .map(r => { + return { + start: r.start, + end: r.end, + mode: r.languageId && modes[r.languageId], + attributeValue: r.attributeValue + }; + }); + }, + getAllModesInDocument(document: TextDocument): LanguageMode[] { + let result = []; + for (let languageId of documentRegions.get(document).getLanguagesInDocument()) { + let mode = modes[languageId]; + if (mode) { + result.push(mode); + } + } + return result; + }, + getAllModes(): LanguageMode[] { + let result = []; + for (let languageId in modes) { + let mode = modes[languageId]; + if (mode) { + result.push(mode); + } + } + return result; + }, + getMode(languageId: string): LanguageMode { + return modes[languageId]; + }, + onDocumentRemoved(document: TextDocument) { + modelCaches.forEach(mc => mc.onDocumentRemoved(document)); + for (let mode in modes) { + modes[mode].onDocumentRemoved(document); + } + }, + dispose(): void { + modelCaches.forEach(mc => mc.dispose()); + modelCaches = []; + for (let mode in modes) { + modes[mode].dispose(); + } + modes = {}; + } + }; +} diff --git a/lsp-embedded-request-forwarding/server/src/modes/htmlMode.ts b/lsp-embedded-request-forwarding/server/src/modes/htmlMode.ts new file mode 100644 index 000000000..9c4adb760 --- /dev/null +++ b/lsp-embedded-request-forwarding/server/src/modes/htmlMode.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { + LanguageMode, + LanguageService as HTMLLanguageService, + Position, + TextDocument +} from '../languageModes'; + +export function getHTMLMode(htmlLanguageService: HTMLLanguageService): LanguageMode { + return { + getId() { + return 'html'; + }, + doComplete(document: TextDocument, position: Position) { + return htmlLanguageService.doComplete( + document, + position, + htmlLanguageService.parseHTMLDocument(document) + ); + }, + onDocumentRemoved(_document: TextDocument) {}, + dispose() {} + }; +} diff --git a/lsp-embedded-request-forwarding/server/src/server.ts b/lsp-embedded-request-forwarding/server/src/server.ts new file mode 100644 index 000000000..a8733147e --- /dev/null +++ b/lsp-embedded-request-forwarding/server/src/server.ts @@ -0,0 +1,111 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * ------------------------------------------------------------------------------------------ */ + +import { + CompletionList, + createConnection, + Diagnostic, + InitializeParams, + ProposedFeatures, + TextDocument, + TextDocuments +} from 'vscode-languageserver'; +import { getLanguageModes, LanguageModes } from './languageModes'; + +// Create a connection for the server. The connection uses Node's IPC as a transport. +// Also include all preview / proposed LSP features. +let connection = createConnection(ProposedFeatures.all); + +// Create a simple text document manager. The text document manager +// supports full document sync only +let documents: TextDocuments = new TextDocuments(); + +let languageModes: LanguageModes; + +connection.onInitialize((_params: InitializeParams) => { + languageModes = getLanguageModes(); + + documents.onDidClose(e => { + languageModes.onDocumentRemoved(e.document); + }); + connection.onShutdown(() => { + languageModes.dispose(); + }); + + return { + capabilities: { + textDocumentSync: documents.syncKind, + // Tell the client that the server supports code completion + completionProvider: { + resolveProvider: false + } + } + }; +}); + +connection.onInitialized(() => {}); + +connection.onDidChangeConfiguration(_change => { + // Revalidate all open text documents + documents.all().forEach(validateTextDocument); +}); + +// The content of a text document has changed. This event is emitted +// when the text document first opened or when its content has changed. +documents.onDidChangeContent(change => { + validateTextDocument(change.document); +}); + +async function validateTextDocument(textDocument: TextDocument) { + try { + const version = textDocument.version; + const diagnostics: Diagnostic[] = []; + if (textDocument.languageId === 'html') { + const modes = languageModes.getAllModesInDocument(textDocument); + const latestTextDocument = documents.get(textDocument.uri); + if (latestTextDocument && latestTextDocument.version === version) { + // check no new version has come in after in after the async op + modes.forEach(mode => { + if (mode.doValidation) { + mode.doValidation(latestTextDocument).forEach(d => { + diagnostics.push(d); + }); + } + }); + connection.sendDiagnostics({ uri: latestTextDocument.uri, diagnostics }); + } + } + } catch (e) { + connection.console.error(`Error while validating ${textDocument.uri}`); + connection.console.error(e); + } +} + +connection.onDidChangeWatchedFiles(_change => { + // Monitored files have change in VSCode + connection.console.log('We received an file change event'); +}); + +// This handler provides the initial list of the completion items. +connection.onCompletion(async (textDocumentPosition, token) => { + const document = documents.get(textDocumentPosition.textDocument.uri); + if (!document) { + return null; + } + const mode = languageModes.getModeAtPosition(document, textDocumentPosition.position); + if (!mode || !mode.doComplete) { + return CompletionList.create(); + } + const doComplete = mode.doComplete!; + + return doComplete(document, textDocumentPosition.position); +}); + +// Make the text document manager listen on the connection +// for open, change and close text document events +documents.listen(connection); + +// Listen on the connection +connection.listen(); diff --git a/lsp-embedded-request-forwarding/server/tsconfig.json b/lsp-embedded-request-forwarding/server/tsconfig.json new file mode 100644 index 000000000..974842f3a --- /dev/null +++ b/lsp-embedded-request-forwarding/server/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "strict": true, + "outDir": "out", + "rootDir": "src" + }, + "include": ["src"], + "exclude": ["node_modules", ".vscode-test"] +} diff --git a/lsp-embedded-request-forwarding/tsconfig.json b/lsp-embedded-request-forwarding/tsconfig.json new file mode 100644 index 000000000..a1a93135c --- /dev/null +++ b/lsp-embedded-request-forwarding/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "outDir": "out", + "rootDir": "src", + "sourceMap": true + }, + "include": [ + "src" + ], + "exclude": [ + "node_modules", + ".vscode-test" + ], + "references": [ + { "path": "./client" }, + { "path": "./server" } + ] +} \ No newline at end of file diff --git a/lsp-embedded-request-forwarding/tslint.json b/lsp-embedded-request-forwarding/tslint.json new file mode 100644 index 000000000..0ab0ca6e8 --- /dev/null +++ b/lsp-embedded-request-forwarding/tslint.json @@ -0,0 +1,6 @@ +{ + "rules": { + "indent": [true, "tabs"], + "semicolon": [true, "always"] + } +} \ No newline at end of file