diff --git a/.no-sublime-package b/.no-sublime-package deleted file mode 100644 index e69de29..0000000 diff --git a/LSP-html.sublime-commands b/LSP-html.sublime-commands new file mode 100644 index 0000000..2011e28 --- /dev/null +++ b/LSP-html.sublime-commands @@ -0,0 +1,10 @@ +[ + { + "caption": "Preferences: LSP-html Settings", + "command": "edit_settings", + "args": { + "base_file": "${packages}/LSP-html/LSP-html.sublime-settings", + "default": "// Settings in here override those in \"LSP-html/LSP-html.sublime-settings\",\n\n{\n\t$0\n}\n" + } + }, +] diff --git a/LSP-html.sublime-settings b/LSP-html.sublime-settings index 5ef888a..853eb72 100644 --- a/LSP-html.sublime-settings +++ b/LSP-html.sublime-settings @@ -1,17 +1,30 @@ { - "client" : { - "enabled": true, - "languages": [ - { - "languageId": "html", - "scopes": ["text.html.basic"], - "syntaxes": [ - "Packages/HTML/HTML.sublime-syntax", - "Packages/PHP/PHP.sublime-syntax" - ] - } - ], - "initializationOptions": {}, - "settings": {} - } -} \ No newline at end of file + "enabled": true, + "languages": [ + { + "languageId": "html", + "scopes": [ + "text.html.basic", + ], + "syntaxes": [ + "Packages/HTML/HTML.sublime-syntax", + "Packages/PHP/PHP.sublime-syntax", + ], + }, + ], + "initializationOptions": { + "provideFormatter": true, + "embeddedLanguages": { + "css": true, + "html": true, + "javascript": true, + }, + }, + "settings": { + "html": { + "format": { + "enable": true, + }, + }, + }, +} diff --git a/Main.sublime-menu b/Main.sublime-menu index e93b93b..72aca86 100644 --- a/Main.sublime-menu +++ b/Main.sublime-menu @@ -20,15 +20,15 @@ "command": "edit_settings", "args": { "base_file": "${packages}/LSP-html/LSP-html.sublime-settings", - "default": "{\n\t$0\n}\n" - } - } - ] - } - ] - } - ] - } - ] - } + "default": "// Settings in here override those in \"LSP-html/LSP-html.sublime-settings\",\n\n{\n\t$0\n}\n", + }, + }, + ], + }, + ], + }, + ], + }, + ], + }, ] diff --git a/dependencies.json b/dependencies.json new file mode 100644 index 0000000..3d55a18 --- /dev/null +++ b/dependencies.json @@ -0,0 +1,8 @@ +{ + "*": { + "*": [ + "lsp_utils", + "sublime_lib" + ] + } +} diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 01e2d92..0000000 --- a/package-lock.json +++ /dev/null @@ -1,108 +0,0 @@ -{ - "name": "lsp-html", - "version": "0.0.1", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "typescript": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz", - "integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==" - }, - "vscode-css-languageservice": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-3.0.13.tgz", - "integrity": "sha512-RWkO/c/A7iXhHEy3OuEqkCqavDjpD4NF2Ca8vjai+ZtEYNeHrm1ybTnBYLP4Ft1uXvvaaVtYA9HrDjD6+CUONg==", - "requires": { - "vscode-languageserver-types": "^3.13.0", - "vscode-nls": "^4.0.0" - }, - "dependencies": { - "vscode-nls": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-4.1.1.tgz", - "integrity": "sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A==" - } - } - }, - "vscode-html-languageserver-bin": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/vscode-html-languageserver-bin/-/vscode-html-languageserver-bin-1.4.0.tgz", - "integrity": "sha512-rDnpvASQwf1dlRaGiu8edo5WlAr4dM3/r/dcPCH4O6UD4+eShhdC1E8IyiSisnJU6bRk+4mDTCgA6cyhGJY2xA==", - "requires": { - "typescript": "^2.9.1", - "vscode-css-languageservice": "^3.0.9-next.18", - "vscode-html-languageservice": "^2.1.3-next.5", - "vscode-languageserver": "^4.1.3", - "vscode-languageserver-protocol-foldingprovider": "^2.0.1", - "vscode-languageserver-types": "^3.7.2", - "vscode-nls": "^3.2.2", - "vscode-uri": "^1.0.3" - } - }, - "vscode-html-languageservice": { - "version": "2.1.12", - "resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-2.1.12.tgz", - "integrity": "sha512-mIb5VMXM5jI97HzCk2eadI1K//rCEZXte0wBqA7PGXsyJH4KTyJUaYk9MR+mbfpUl2vMi3HZw9GUOLGYLc6l5w==", - "requires": { - "vscode-languageserver-types": "^3.13.0", - "vscode-nls": "^4.0.0", - "vscode-uri": "^1.0.6" - }, - "dependencies": { - "vscode-nls": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-4.1.1.tgz", - "integrity": "sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A==" - } - } - }, - "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": "4.4.2", - "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-4.4.2.tgz", - "integrity": "sha512-61y8Raevi9EigDgg9NelvT9cUAohiEbUl1LOwQQgOCAaNX62yKny/ddi0uC+FUTm4CzsjhBu+06R+vYgfCYReA==", - "requires": { - "vscode-languageserver-protocol": "^3.10.3", - "vscode-uri": "^1.0.5" - } - }, - "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-protocol-foldingprovider": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol-foldingprovider/-/vscode-languageserver-protocol-foldingprovider-2.0.1.tgz", - "integrity": "sha512-N8bOS8i0xuQMn/y0bijyefDbOsMl6hiH6LDREYWavTLTM5jbj44EiQfStsbmAv/0eaFKkL/jf5hW7nWwBy2HBw==", - "requires": { - "vscode-languageserver-protocol": "^3.7.2", - "vscode-languageserver-types": "^3.7.2" - } - }, - "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": "3.2.5", - "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-3.2.5.tgz", - "integrity": "sha512-ITtoh3V4AkWXMmp3TB97vsMaHRgHhsSFPsUdzlueSL+dRZbSNTZeOmdQv60kjCV306ghPxhDeoNUEm3+EZMuyw==" - }, - "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/package.json b/package.json deleted file mode 100644 index d09b876..0000000 --- a/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "lsp-html", - "version": "0.0.1", - "description": "HTML support for Sublime LSP", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/sublimelsp/LSP-html.git" - }, - "author": "Предраг Николић", - "license": "MIT", - "bugs": { - "url": "https://github.com/sublimelsp/LSP-html/issues" - }, - "homepage": "https://github.com/sublimelsp/LSP-html#readme", - "dependencies": { - "vscode-html-languageserver-bin": "^1.4.0" - } -} diff --git a/plugin.py b/plugin.py index 060bf04..bb65961 100644 --- a/plugin.py +++ b/plugin.py @@ -1,97 +1,73 @@ import shutil import os import sublime -import threading -import subprocess from LSP.plugin.core.handlers import LanguageHandler -from LSP.plugin.core.settings import ClientConfig, LanguageConfig, read_client_config +from LSP.plugin.core.settings import ClientConfig, read_client_config +from lsp_utils import ServerNpmResource + +PACKAGE_NAME = 'LSP-html' +SETTINGS_FILENAME = 'LSP-html.sublime-settings' +SERVER_DIRECTORY = 'vscode-html' +SERVER_BINARY_PATH = os.path.join(SERVER_DIRECTORY, 'out', 'htmlServerMain.js') + +server = ServerNpmResource(PACKAGE_NAME, SERVER_DIRECTORY, SERVER_BINARY_PATH) -package_path = os.path.dirname(__file__) -server_path = os.path.join(package_path, 'node_modules', 'vscode-html-languageserver-bin', 'htmlServerMain.js') def plugin_loaded(): - is_server_installed = os.path.isfile(server_path) - print('LSP-html: Server {} installed.'.format('is' if is_server_installed else 'is not' )) - - # install the node_modules if not installed - if not is_server_installed: - # this will be called only when the plugin gets: - # - installed for the first time, - # - or when updated on package control - logAndShowMessage('LSP-html: Installing server.') - - runCommand( - onCommandDone, - ["npm", "install", "--verbose", "--prefix", package_path, package_path] - ) - - -def onCommandDone(): - logAndShowMessage('LSP-html: Server installed.') - - -def runCommand(onExit, popenArgs): - """ - Runs the given args in a subprocess.Popen, and then calls the function - onExit when the subprocess completes. - onExit is a callable object, and popenArgs is a list/tuple of args that - would give to subprocess.Popen. - """ - def runInThread(onExit, popenArgs): - try: - if sublime.platform() == 'windows': - subprocess.check_call(popenArgs, shell=True) - else: - subprocess.check_call(popenArgs) - onExit() - except subprocess.CalledProcessError as error: - logAndShowMessage('LSP-html: Error while installing the server.', error) - return - thread = threading.Thread(target=runInThread, args=(onExit, popenArgs)) - thread.start() - # returns immediately after the thread starts - return thread + server.setup() -def is_node_installed(): - return shutil.which('node') is not None +def plugin_unloaded(): + server.cleanup() -def logAndShowMessage(msg, additional_logs=None): - print(msg, '\n', additional_logs) if additional_logs else print(msg) - sublime.active_window().status_message(msg) +def is_node_installed(): + return shutil.which('node') is not None class LspHtmlPlugin(LanguageHandler): @property def name(self) -> str: - return 'lsp-html' + return PACKAGE_NAME.lower() @property def config(self) -> ClientConfig: - settings = sublime.load_settings("LSP-html.sublime-settings") - client_configuration = settings.get('client') + # Calling setup() also here as this might run before `plugin_loaded`. + # Will be a no-op if already ran. + # See https://github.com/sublimelsp/LSP/issues/899 + server.setup() + + configuration = self.migrate_and_read_configuration() + default_configuration = { - "command": [ - 'node', - server_path, - '--stdio' - ], - "languages": [ - { - "languageId": "html", - "scopes": ["text.html.basic"], - "syntaxes": [ - "Packages/HTML/HTML.sublime-syntax", - "Packages/PHP/PHP.sublime-syntax" - ] - } - ] + 'enabled': True, + 'command': ['node', server.binary_path, '--stdio'], } - default_configuration.update(client_configuration) + + default_configuration.update(configuration) + return read_client_config('lsp-html', default_configuration) + def migrate_and_read_configuration(self) -> dict: + settings = {} + loaded_settings = sublime.load_settings(SETTINGS_FILENAME) + + if loaded_settings: + if loaded_settings.has('client'): + client = loaded_settings.get('client') + loaded_settings.erase('client') + # Migrate old keys + for key in client: + loaded_settings.set(key, client[key]) + sublime.save_settings(SETTINGS_FILENAME) + + # Read configuration keys + for key in ['languages', 'initializationOptions', 'settings']: + settings[key] = loaded_settings.get(key) + + return settings + def on_start(self, window) -> bool: if not is_node_installed(): sublime.status_message('Please install Node.js for the HTML Language Server to work.') diff --git a/vscode-html/.gitignore b/vscode-html/.gitignore new file mode 100644 index 0000000..654f70a --- /dev/null +++ b/vscode-html/.gitignore @@ -0,0 +1,4 @@ +/node_modules/ + +# downloaded source codes +/vscode-html-languageserver/ diff --git a/vscode-html/README.md b/vscode-html/README.md new file mode 100644 index 0000000..126acd6 --- /dev/null +++ b/vscode-html/README.md @@ -0,0 +1,11 @@ +This repo contains built [vscode-html-languageserver](https://github.com/vscode-langservers/vscode-html-languageserver). + + +# Build + +Just run `compile-vscode-html-languageserver.sh`. The built result will be in `out/`. + + +# References + +- https://github.com/mattn/vim-lsp-settings/pull/48 diff --git a/vscode-html/compile-vscode-html-languageserver.sh b/vscode-html/compile-vscode-html-languageserver.sh new file mode 100755 index 0000000..884693a --- /dev/null +++ b/vscode-html/compile-vscode-html-languageserver.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash +# @see https://github.com/mattn/vim-lsp-settings/pull/48 + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +REPO_DIR="${SCRIPT_DIR}" +SRC_DIR="${REPO_DIR}/vscode-html-languageserver" +DIST_DIR="${REPO_DIR}/out" + + +# ------------------------- # +# download the source codes # +# ------------------------- # + +pushd "${REPO_DIR}" || exit + +rm -rf \ + "${SRC_DIR}" "${DIST_DIR}" \ + "package.json" "package-lock.json" + +# or get the source via git clone +# git clone --depth=1 https://github.com/vscode-langservers/vscode-html-languageserver "${SRC_DIR}" + +wget https://github.com/vscode-langservers/vscode-html-languageserver/archive/master.zip -O src.zip +unzip src.zip +rm -f src.zip +mv vscode-html-languageserver-master "${SRC_DIR}" + +popd || exit + + +# ------------ # +# prepare deps # +# ------------ # + +pushd "${SRC_DIR}" || exit + +npm install +npm install typescript + +popd || exit + + +# ------- # +# compile # +# ------- # + +pushd "${SRC_DIR}" || exit + +cat << EOF > tsconfig.json +{ + "compilerOptions": { + "target": "es2018", + "module": "commonjs", + "strict": true, + "alwaysStrict": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "outDir": "./out" + }, + "files": [ + "src/htmlServerMain.ts" + ] +} +EOF + +npx tsc --newLine LF -p . + +popd || exit + + +# -------------------- # +# collect output files # +# -------------------- # + +pushd "${REPO_DIR}" || exit + +mv "${SRC_DIR}/out" "${DIST_DIR}" +cp "${SRC_DIR}/package.json" . +cp "${SRC_DIR}/package-lock.json" . + +popd || exit diff --git a/vscode-html/out/customData.js b/vscode-html/out/customData.js new file mode 100644 index 0000000..3fe2702 --- /dev/null +++ b/vscode-html/out/customData.js @@ -0,0 +1,27 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +const vscode_html_languageservice_1 = require("vscode-html-languageservice"); +const fs = require("fs"); +function getDataProviders(dataPaths) { + if (!dataPaths) { + return []; + } + const providers = []; + dataPaths.forEach((path, i) => { + try { + if (fs.existsSync(path)) { + const htmlData = JSON.parse(fs.readFileSync(path, 'utf-8')); + providers.push(vscode_html_languageservice_1.newHTMLDataProvider(`customProvider${i}`, htmlData)); + } + } + catch (err) { + console.log(`Failed to load tag from ${path}`); + } + }); + return providers; +} +exports.getDataProviders = getDataProviders; diff --git a/vscode-html/out/htmlServerMain.js b/vscode-html/out/htmlServerMain.js new file mode 100644 index 0000000..f4b3279 --- /dev/null +++ b/vscode-html/out/htmlServerMain.js @@ -0,0 +1,478 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +const vscode_languageserver_1 = require("vscode-languageserver"); +const languageModes_1 = require("./modes/languageModes"); +const formatting_1 = require("./modes/formatting"); +const arrays_1 = require("./utils/arrays"); +const documentContext_1 = require("./utils/documentContext"); +const vscode_uri_1 = require("vscode-uri"); +const runner_1 = require("./utils/runner"); +const htmlFolding_1 = require("./modes/htmlFolding"); +const customData_1 = require("./customData"); +const selectionRanges_1 = require("./modes/selectionRanges"); +const semanticTokens_1 = require("./modes/semanticTokens"); +var TagCloseRequest; +(function (TagCloseRequest) { + TagCloseRequest.type = new vscode_languageserver_1.RequestType('html/tag'); +})(TagCloseRequest || (TagCloseRequest = {})); +var MatchingTagPositionRequest; +(function (MatchingTagPositionRequest) { + MatchingTagPositionRequest.type = new vscode_languageserver_1.RequestType('html/matchingTagPosition'); +})(MatchingTagPositionRequest || (MatchingTagPositionRequest = {})); +var SemanticTokenRequest; +(function (SemanticTokenRequest) { + SemanticTokenRequest.type = new vscode_languageserver_1.RequestType('html/semanticTokens'); +})(SemanticTokenRequest || (SemanticTokenRequest = {})); +var SemanticTokenLegendRequest; +(function (SemanticTokenLegendRequest) { + SemanticTokenLegendRequest.type = new vscode_languageserver_1.RequestType('html/semanticTokenLegend'); +})(SemanticTokenLegendRequest || (SemanticTokenLegendRequest = {})); +// Create a connection for the server +const connection = vscode_languageserver_1.createConnection(); +console.log = connection.console.log.bind(connection.console); +console.error = connection.console.error.bind(connection.console); +process.on('unhandledRejection', (e) => { + console.error(runner_1.formatError(`Unhandled exception`, e)); +}); +process.on('uncaughtException', (e) => { + console.error(runner_1.formatError(`Unhandled exception`, e)); +}); +// Create a text document manager. +const documents = new vscode_languageserver_1.TextDocuments(languageModes_1.TextDocument); +// Make the text document manager listen on the connection +// for open, change and close text document events +documents.listen(connection); +let workspaceFolders = []; +let languageModes; +let clientSnippetSupport = false; +let dynamicFormatterRegistration = false; +let scopedSettingsSupport = false; +let workspaceFoldersSupport = false; +let foldingRangeLimit = Number.MAX_VALUE; +let globalSettings = {}; +let documentSettings = {}; +// remove document settings on close +documents.onDidClose(e => { + delete documentSettings[e.document.uri]; +}); +function getDocumentSettings(textDocument, needsDocumentSettings) { + if (scopedSettingsSupport && needsDocumentSettings()) { + let promise = documentSettings[textDocument.uri]; + if (!promise) { + const scopeUri = textDocument.uri; + const configRequestParam = { items: [{ scopeUri, section: 'css' }, { scopeUri, section: 'html' }, { scopeUri, section: 'javascript' }] }; + promise = connection.sendRequest(vscode_languageserver_1.ConfigurationRequest.type, configRequestParam).then(s => ({ css: s[0], html: s[1], javascript: s[2] })); + documentSettings[textDocument.uri] = promise; + } + return promise; + } + return Promise.resolve(undefined); +} +// After the server has started the client sends an initialize request. The server receives +// in the passed params the rootPath of the workspace plus the client capabilities +connection.onInitialize((params) => { + const initializationOptions = params.initializationOptions; + workspaceFolders = params.workspaceFolders; + if (!Array.isArray(workspaceFolders)) { + workspaceFolders = []; + if (params.rootPath) { + workspaceFolders.push({ name: '', uri: vscode_uri_1.URI.file(params.rootPath).toString() }); + } + } + const dataPaths = params.initializationOptions.dataPaths; + const providers = customData_1.getDataProviders(dataPaths); + const workspace = { + get settings() { return globalSettings; }, + get folders() { return workspaceFolders; } + }; + languageModes = languageModes_1.getLanguageModes(initializationOptions ? initializationOptions.embeddedLanguages : { css: true, javascript: true }, workspace, params.capabilities, providers); + documents.onDidClose(e => { + languageModes.onDocumentRemoved(e.document); + }); + connection.onShutdown(() => { + languageModes.dispose(); + }); + function getClientCapability(name, def) { + const keys = name.split('.'); + let c = params.capabilities; + for (let i = 0; c && i < keys.length; i++) { + if (!c.hasOwnProperty(keys[i])) { + return def; + } + c = c[keys[i]]; + } + return c; + } + clientSnippetSupport = getClientCapability('textDocument.completion.completionItem.snippetSupport', false); + dynamicFormatterRegistration = getClientCapability('textDocument.rangeFormatting.dynamicRegistration', false) && (typeof params.initializationOptions.provideFormatter !== 'boolean'); + scopedSettingsSupport = getClientCapability('workspace.configuration', false); + workspaceFoldersSupport = getClientCapability('workspace.workspaceFolders', false); + foldingRangeLimit = getClientCapability('textDocument.foldingRange.rangeLimit', Number.MAX_VALUE); + const capabilities = { + textDocumentSync: vscode_languageserver_1.TextDocumentSyncKind.Incremental, + completionProvider: clientSnippetSupport ? { resolveProvider: true, triggerCharacters: ['.', ':', '<', '"', '=', '/'] } : undefined, + hoverProvider: true, + documentHighlightProvider: true, + documentRangeFormattingProvider: params.initializationOptions.provideFormatter === true, + documentLinkProvider: { resolveProvider: false }, + documentSymbolProvider: true, + definitionProvider: true, + signatureHelpProvider: { triggerCharacters: ['('] }, + referencesProvider: true, + colorProvider: {}, + foldingRangeProvider: true, + selectionRangeProvider: true, + renameProvider: true + }; + return { capabilities }; +}); +connection.onInitialized(() => { + if (workspaceFoldersSupport) { + connection.client.register(vscode_languageserver_1.DidChangeWorkspaceFoldersNotification.type); + connection.onNotification(vscode_languageserver_1.DidChangeWorkspaceFoldersNotification.type, e => { + const toAdd = e.event.added; + const toRemove = e.event.removed; + const updatedFolders = []; + if (workspaceFolders) { + for (const folder of workspaceFolders) { + if (!toRemove.some(r => r.uri === folder.uri) && !toAdd.some(r => r.uri === folder.uri)) { + updatedFolders.push(folder); + } + } + } + workspaceFolders = updatedFolders.concat(toAdd); + documents.all().forEach(triggerValidation); + }); + } +}); +let formatterRegistration = null; +// The settings have changed. Is send on server activation as well. +connection.onDidChangeConfiguration((change) => { + globalSettings = change.settings; + documentSettings = {}; // reset all document settings + documents.all().forEach(triggerValidation); + // dynamically enable & disable the formatter + if (dynamicFormatterRegistration) { + const enableFormatter = globalSettings && globalSettings.html && globalSettings.html.format && globalSettings.html.format.enable; + if (enableFormatter) { + if (!formatterRegistration) { + const documentSelector = [{ language: 'html' }, { language: 'handlebars' }]; + formatterRegistration = connection.client.register(vscode_languageserver_1.DocumentRangeFormattingRequest.type, { documentSelector }); + } + } + else if (formatterRegistration) { + formatterRegistration.then(r => r.dispose()); + formatterRegistration = null; + } + } +}); +const pendingValidationRequests = {}; +const validationDelayMs = 500; +// 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 => { + triggerValidation(change.document); +}); +// a document has closed: clear all diagnostics +documents.onDidClose(event => { + cleanPendingValidation(event.document); + connection.sendDiagnostics({ uri: event.document.uri, diagnostics: [] }); +}); +function cleanPendingValidation(textDocument) { + const request = pendingValidationRequests[textDocument.uri]; + if (request) { + clearTimeout(request); + delete pendingValidationRequests[textDocument.uri]; + } +} +function triggerValidation(textDocument) { + cleanPendingValidation(textDocument); + pendingValidationRequests[textDocument.uri] = setTimeout(() => { + delete pendingValidationRequests[textDocument.uri]; + validateTextDocument(textDocument); + }, validationDelayMs); +} +function isValidationEnabled(languageId, settings = globalSettings) { + const validationSettings = settings && settings.html && settings.html.validate; + if (validationSettings) { + return languageId === 'css' && validationSettings.styles !== false || languageId === 'javascript' && validationSettings.scripts !== false; + } + return true; +} +async function validateTextDocument(textDocument) { + try { + const version = textDocument.version; + const diagnostics = []; + if (textDocument.languageId === 'html') { + const modes = languageModes.getAllModesInDocument(textDocument); + const settings = await getDocumentSettings(textDocument, () => modes.some(m => !!m.doValidation)); + 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 && isValidationEnabled(mode.getId(), settings)) { + arrays_1.pushAll(diagnostics, mode.doValidation(latestTextDocument, settings)); + } + }); + connection.sendDiagnostics({ uri: latestTextDocument.uri, diagnostics }); + } + } + } + catch (e) { + connection.console.error(runner_1.formatError(`Error while validating ${textDocument.uri}`, e)); + } +} +connection.onCompletion(async (textDocumentPosition, token) => { + return runner_1.runSafeAsync(async () => { + const document = documents.get(textDocumentPosition.textDocument.uri); + if (!document) { + return null; + } + const mode = languageModes.getModeAtPosition(document, textDocumentPosition.position); + if (!mode || !mode.doComplete) { + return { isIncomplete: true, items: [] }; + } + const doComplete = mode.doComplete; + if (mode.getId() !== 'html') { + /* __GDPR__ + "html.embbedded.complete" : { + "languageId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + connection.telemetry.logEvent({ key: 'html.embbedded.complete', value: { languageId: mode.getId() } }); + } + const settings = await getDocumentSettings(document, () => doComplete.length > 2); + const result = doComplete(document, textDocumentPosition.position, settings); + return result; + }, null, `Error while computing completions for ${textDocumentPosition.textDocument.uri}`, token); +}); +connection.onCompletionResolve((item, token) => { + return runner_1.runSafe(() => { + const data = item.data; + if (data && data.languageId && data.uri) { + const mode = languageModes.getMode(data.languageId); + const document = documents.get(data.uri); + if (mode && mode.doResolve && document) { + return mode.doResolve(document, item); + } + } + return item; + }, item, `Error while resolving completion proposal`, token); +}); +connection.onHover((textDocumentPosition, token) => { + return runner_1.runSafe(() => { + const document = documents.get(textDocumentPosition.textDocument.uri); + if (document) { + const mode = languageModes.getModeAtPosition(document, textDocumentPosition.position); + if (mode && mode.doHover) { + return mode.doHover(document, textDocumentPosition.position); + } + } + return null; + }, null, `Error while computing hover for ${textDocumentPosition.textDocument.uri}`, token); +}); +connection.onDocumentHighlight((documentHighlightParams, token) => { + return runner_1.runSafe(() => { + const document = documents.get(documentHighlightParams.textDocument.uri); + if (document) { + const mode = languageModes.getModeAtPosition(document, documentHighlightParams.position); + if (mode && mode.findDocumentHighlight) { + return mode.findDocumentHighlight(document, documentHighlightParams.position); + } + } + return []; + }, [], `Error while computing document highlights for ${documentHighlightParams.textDocument.uri}`, token); +}); +connection.onDefinition((definitionParams, token) => { + return runner_1.runSafe(() => { + const document = documents.get(definitionParams.textDocument.uri); + if (document) { + const mode = languageModes.getModeAtPosition(document, definitionParams.position); + if (mode && mode.findDefinition) { + return mode.findDefinition(document, definitionParams.position); + } + } + return []; + }, null, `Error while computing definitions for ${definitionParams.textDocument.uri}`, token); +}); +connection.onReferences((referenceParams, token) => { + return runner_1.runSafe(() => { + const document = documents.get(referenceParams.textDocument.uri); + if (document) { + const mode = languageModes.getModeAtPosition(document, referenceParams.position); + if (mode && mode.findReferences) { + return mode.findReferences(document, referenceParams.position); + } + } + return []; + }, [], `Error while computing references for ${referenceParams.textDocument.uri}`, token); +}); +connection.onSignatureHelp((signatureHelpParms, token) => { + return runner_1.runSafe(() => { + const document = documents.get(signatureHelpParms.textDocument.uri); + if (document) { + const mode = languageModes.getModeAtPosition(document, signatureHelpParms.position); + if (mode && mode.doSignatureHelp) { + return mode.doSignatureHelp(document, signatureHelpParms.position); + } + } + return null; + }, null, `Error while computing signature help for ${signatureHelpParms.textDocument.uri}`, token); +}); +connection.onDocumentRangeFormatting(async (formatParams, token) => { + return runner_1.runSafeAsync(async () => { + const document = documents.get(formatParams.textDocument.uri); + if (document) { + let settings = await getDocumentSettings(document, () => true); + if (!settings) { + settings = globalSettings; + } + const unformattedTags = settings && settings.html && settings.html.format && settings.html.format.unformatted || ''; + const enabledModes = { css: !unformattedTags.match(/\bstyle\b/), javascript: !unformattedTags.match(/\bscript\b/) }; + return formatting_1.format(languageModes, document, formatParams.range, formatParams.options, settings, enabledModes); + } + return []; + }, [], `Error while formatting range for ${formatParams.textDocument.uri}`, token); +}); +connection.onDocumentLinks((documentLinkParam, token) => { + return runner_1.runSafe(() => { + const document = documents.get(documentLinkParam.textDocument.uri); + const links = []; + if (document) { + const documentContext = documentContext_1.getDocumentContext(document.uri, workspaceFolders); + languageModes.getAllModesInDocument(document).forEach(m => { + if (m.findDocumentLinks) { + arrays_1.pushAll(links, m.findDocumentLinks(document, documentContext)); + } + }); + } + return links; + }, [], `Error while document links for ${documentLinkParam.textDocument.uri}`, token); +}); +connection.onDocumentSymbol((documentSymbolParms, token) => { + return runner_1.runSafe(() => { + const document = documents.get(documentSymbolParms.textDocument.uri); + const symbols = []; + if (document) { + languageModes.getAllModesInDocument(document).forEach(m => { + if (m.findDocumentSymbols) { + arrays_1.pushAll(symbols, m.findDocumentSymbols(document)); + } + }); + } + return symbols; + }, [], `Error while computing document symbols for ${documentSymbolParms.textDocument.uri}`, token); +}); +connection.onRequest(vscode_languageserver_1.DocumentColorRequest.type, (params, token) => { + return runner_1.runSafe(() => { + const infos = []; + const document = documents.get(params.textDocument.uri); + if (document) { + languageModes.getAllModesInDocument(document).forEach(m => { + if (m.findDocumentColors) { + arrays_1.pushAll(infos, m.findDocumentColors(document)); + } + }); + } + return infos; + }, [], `Error while computing document colors for ${params.textDocument.uri}`, token); +}); +connection.onRequest(vscode_languageserver_1.ColorPresentationRequest.type, (params, token) => { + return runner_1.runSafe(() => { + const document = documents.get(params.textDocument.uri); + if (document) { + const mode = languageModes.getModeAtPosition(document, params.range.start); + if (mode && mode.getColorPresentations) { + return mode.getColorPresentations(document, params.color, params.range); + } + } + return []; + }, [], `Error while computing color presentations for ${params.textDocument.uri}`, token); +}); +connection.onRequest(TagCloseRequest.type, (params, token) => { + return runner_1.runSafe(() => { + const document = documents.get(params.textDocument.uri); + if (document) { + const pos = params.position; + if (pos.character > 0) { + const mode = languageModes.getModeAtPosition(document, languageModes_1.Position.create(pos.line, pos.character - 1)); + if (mode && mode.doAutoClose) { + return mode.doAutoClose(document, pos); + } + } + } + return null; + }, null, `Error while computing tag close actions for ${params.textDocument.uri}`, token); +}); +connection.onFoldingRanges((params, token) => { + return runner_1.runSafe(() => { + const document = documents.get(params.textDocument.uri); + if (document) { + return htmlFolding_1.getFoldingRanges(languageModes, document, foldingRangeLimit, token); + } + return null; + }, null, `Error while computing folding regions for ${params.textDocument.uri}`, token); +}); +connection.onSelectionRanges((params, token) => { + return runner_1.runSafe(() => { + const document = documents.get(params.textDocument.uri); + if (document) { + return selectionRanges_1.getSelectionRanges(languageModes, document, params.positions); + } + return []; + }, [], `Error while computing selection ranges for ${params.textDocument.uri}`, token); +}); +connection.onRenameRequest((params, token) => { + return runner_1.runSafe(() => { + const document = documents.get(params.textDocument.uri); + const position = params.position; + if (document) { + const htmlMode = languageModes.getMode('html'); + if (htmlMode && htmlMode.doRename) { + return htmlMode.doRename(document, position, params.newName); + } + } + return null; + }, null, `Error while computing rename for ${params.textDocument.uri}`, token); +}); +connection.onRequest(MatchingTagPositionRequest.type, (params, token) => { + return runner_1.runSafe(() => { + const document = documents.get(params.textDocument.uri); + if (document) { + const pos = params.position; + if (pos.character > 0) { + const mode = languageModes.getModeAtPosition(document, languageModes_1.Position.create(pos.line, pos.character - 1)); + if (mode && mode.findMatchingTagPosition) { + return mode.findMatchingTagPosition(document, pos); + } + } + } + return null; + }, null, `Error while computing matching tag position for ${params.textDocument.uri}`, token); +}); +let semanticTokensProvider; +function getSemanticTokenProvider() { + if (!semanticTokensProvider) { + semanticTokensProvider = semanticTokens_1.newSemanticTokenProvider(languageModes); + } + return semanticTokensProvider; +} +connection.onRequest(SemanticTokenRequest.type, (params, token) => { + return runner_1.runSafe(() => { + const document = documents.get(params.textDocument.uri); + if (document) { + return getSemanticTokenProvider().getSemanticTokens(document, params.ranges); + } + return null; + }, null, `Error while computing semantic tokens for ${params.textDocument.uri}`, token); +}); +connection.onRequest(SemanticTokenLegendRequest.type, (_params, token) => { + return runner_1.runSafe(() => { + return getSemanticTokenProvider().legend; + }, null, `Error while computing semantic tokens legend`, token); +}); +// Listen on the connection +connection.listen(); diff --git a/vscode-html/out/languageModelCache.js b/vscode-html/out/languageModelCache.js new file mode 100644 index 0000000..146b034 --- /dev/null +++ b/vscode-html/out/languageModelCache.js @@ -0,0 +1,72 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +function getLanguageModelCache(maxEntries, cleanupIntervalTimeInSec, parse) { + let languageModels = {}; + let nModels = 0; + let cleanupInterval = 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) { + 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) { + const uri = document.uri; + if (languageModels[uri]) { + delete languageModels[uri]; + nModels--; + } + }, + dispose() { + if (typeof cleanupInterval !== 'undefined') { + clearInterval(cleanupInterval); + cleanupInterval = undefined; + languageModels = {}; + nModels = 0; + } + } + }; +} +exports.getLanguageModelCache = getLanguageModelCache; diff --git a/vscode-html/out/modes/cssMode.js b/vscode-html/out/modes/cssMode.js new file mode 100644 index 0000000..aa1b7b1 --- /dev/null +++ b/vscode-html/out/modes/cssMode.js @@ -0,0 +1,72 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +const languageModelCache_1 = require("../languageModelCache"); +const languageModes_1 = require("./languageModes"); +const embeddedSupport_1 = require("./embeddedSupport"); +function getCSSMode(cssLanguageService, documentRegions, workspace) { + let embeddedCSSDocuments = languageModelCache_1.getLanguageModelCache(10, 60, document => documentRegions.get(document).getEmbeddedDocument('css')); + let cssStylesheets = languageModelCache_1.getLanguageModelCache(10, 60, document => cssLanguageService.parseStylesheet(document)); + return { + getId() { + return 'css'; + }, + doValidation(document, settings = workspace.settings) { + let embedded = embeddedCSSDocuments.get(document); + return cssLanguageService.doValidation(embedded, cssStylesheets.get(embedded), settings && settings.css); + }, + doComplete(document, position, _settings = workspace.settings) { + let embedded = embeddedCSSDocuments.get(document); + const stylesheet = cssStylesheets.get(embedded); + return cssLanguageService.doComplete(embedded, position, stylesheet) || languageModes_1.CompletionList.create(); + }, + doHover(document, position) { + let embedded = embeddedCSSDocuments.get(document); + return cssLanguageService.doHover(embedded, position, cssStylesheets.get(embedded)); + }, + findDocumentHighlight(document, position) { + let embedded = embeddedCSSDocuments.get(document); + return cssLanguageService.findDocumentHighlights(embedded, position, cssStylesheets.get(embedded)); + }, + findDocumentSymbols(document) { + let embedded = embeddedCSSDocuments.get(document); + return cssLanguageService.findDocumentSymbols(embedded, cssStylesheets.get(embedded)).filter(s => s.name !== embeddedSupport_1.CSS_STYLE_RULE); + }, + findDefinition(document, position) { + let embedded = embeddedCSSDocuments.get(document); + return cssLanguageService.findDefinition(embedded, position, cssStylesheets.get(embedded)); + }, + findReferences(document, position) { + let embedded = embeddedCSSDocuments.get(document); + return cssLanguageService.findReferences(embedded, position, cssStylesheets.get(embedded)); + }, + findDocumentColors(document) { + let embedded = embeddedCSSDocuments.get(document); + return cssLanguageService.findDocumentColors(embedded, cssStylesheets.get(embedded)); + }, + getColorPresentations(document, color, range) { + let embedded = embeddedCSSDocuments.get(document); + return cssLanguageService.getColorPresentations(embedded, cssStylesheets.get(embedded), color, range); + }, + getFoldingRanges(document) { + let embedded = embeddedCSSDocuments.get(document); + return cssLanguageService.getFoldingRanges(embedded, {}); + }, + getSelectionRange(document, position) { + let embedded = embeddedCSSDocuments.get(document); + return cssLanguageService.getSelectionRanges(embedded, [position], cssStylesheets.get(embedded))[0]; + }, + onDocumentRemoved(document) { + embeddedCSSDocuments.onDocumentRemoved(document); + cssStylesheets.onDocumentRemoved(document); + }, + dispose() { + embeddedCSSDocuments.dispose(); + cssStylesheets.dispose(); + } + }; +} +exports.getCSSMode = getCSSMode; diff --git a/vscode-html/out/modes/embeddedSupport.js b/vscode-html/out/modes/embeddedSupport.js new file mode 100644 index 0000000..cfac420 --- /dev/null +++ b/vscode-html/out/modes/embeddedSupport.js @@ -0,0 +1,213 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +const languageModes_1 = require("./languageModes"); +exports.CSS_STYLE_RULE = '__'; +function getDocumentRegions(languageService, document) { + let regions = []; + let scanner = languageService.createScanner(document.getText()); + let lastTagName = ''; + let lastAttributeName = null; + let languageIdFromType = undefined; + let importedScripts = []; + let token = scanner.scan(); + while (token !== languageModes_1.TokenType.EOS) { + switch (token) { + case languageModes_1.TokenType.StartTag: + lastTagName = scanner.getTokenText(); + lastAttributeName = null; + languageIdFromType = 'javascript'; + break; + case languageModes_1.TokenType.Styles: + regions.push({ languageId: 'css', start: scanner.getTokenOffset(), end: scanner.getTokenEnd() }); + break; + case languageModes_1.TokenType.Script: + regions.push({ languageId: languageIdFromType, start: scanner.getTokenOffset(), end: scanner.getTokenEnd() }); + break; + case languageModes_1.TokenType.AttributeName: + lastAttributeName = scanner.getTokenText(); + break; + case languageModes_1.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) => getLanguageRanges(document, regions, range), + getEmbeddedDocument: (languageId, ignoreAttributeValues) => getEmbeddedDocument(document, regions, languageId, ignoreAttributeValues), + getLanguageAtPosition: (position) => getLanguageAtPosition(document, regions, position), + getLanguagesInDocument: () => getLanguagesInDocument(document, regions), + getImportedScripts: () => importedScripts + }; +} +exports.getDocumentRegions = getDocumentRegions; +function getLanguageRanges(document, regions, range) { + let result = []; + let currentPos = range ? range.start : languageModes_1.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, regions) { + 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, regions, position) { + 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, contents, languageId, ignoreAttributeValues) { + 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 languageModes_1.TextDocument.create(document.uri, languageId, document.version, result); +} +function getPrefix(c) { + if (c.attributeValue) { + switch (c.languageId) { + case 'css': return exports.CSS_STYLE_RULE + '{'; + } + } + return ''; +} +function getSuffix(c) { + if (c.attributeValue) { + switch (c.languageId) { + case 'css': return '}'; + case 'javascript': return ';'; + } + } + return ''; +} +function substituteWithWhitespace(result, start, end, oldContent, before, after) { + 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, str, n) { + while (n > 0) { + if (n & 1) { + result += str; + } + n >>= 1; + str += str; + } + return result; +} +function getAttributeLanguage(attributeName) { + let match = attributeName.match(/^(style)$|^(on\w+)$/i); + if (!match) { + return null; + } + return match[1] ? 'css' : 'javascript'; +} diff --git a/vscode-html/out/modes/formatting.js b/vscode-html/out/modes/formatting.js new file mode 100644 index 0000000..cfd57c2 --- /dev/null +++ b/vscode-html/out/modes/formatting.js @@ -0,0 +1,82 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +const languageModes_1 = require("./languageModes"); +const arrays_1 = require("../utils/arrays"); +const strings_1 = require("../utils/strings"); +function format(languageModes, document, formatRange, formattingOptions, settings, enabledModes) { + let result = []; + let endPos = formatRange.end; + let endOffset = document.offsetAt(endPos); + let content = document.getText(); + if (endPos.character === 0 && endPos.line > 0 && endOffset !== content.length) { + // if selection ends after a new line, exclude that new line + let prevLineStart = document.offsetAt(languageModes_1.Position.create(endPos.line - 1, 0)); + while (strings_1.isEOL(content, endOffset - 1) && endOffset > prevLineStart) { + endOffset--; + } + formatRange = languageModes_1.Range.create(formatRange.start, document.positionAt(endOffset)); + } + // run the html formatter on the full range and pass the result content to the embedded formatters. + // from the final content create a single edit + // advantages of this approach are + // - correct indents in the html document + // - correct initial indent for embedded formatters + // - no worrying of overlapping edits + // make sure we start in html + let allRanges = languageModes.getModesInRange(document, formatRange); + let i = 0; + let startPos = formatRange.start; + let isHTML = (range) => range.mode && range.mode.getId() === 'html'; + while (i < allRanges.length && !isHTML(allRanges[i])) { + let range = allRanges[i]; + if (!range.attributeValue && range.mode && range.mode.format) { + let edits = range.mode.format(document, languageModes_1.Range.create(startPos, range.end), formattingOptions, settings); + arrays_1.pushAll(result, edits); + } + startPos = range.end; + i++; + } + if (i === allRanges.length) { + return result; + } + // modify the range + formatRange = languageModes_1.Range.create(startPos, formatRange.end); + // perform a html format and apply changes to a new document + let htmlMode = languageModes.getMode('html'); + let htmlEdits = htmlMode.format(document, formatRange, formattingOptions, settings); + let htmlFormattedContent = languageModes_1.TextDocument.applyEdits(document, htmlEdits); + let newDocument = languageModes_1.TextDocument.create(document.uri + '.tmp', document.languageId, document.version, htmlFormattedContent); + try { + // run embedded formatters on html formatted content: - formatters see correct initial indent + let afterFormatRangeLength = document.getText().length - document.offsetAt(formatRange.end); // length of unchanged content after replace range + let newFormatRange = languageModes_1.Range.create(formatRange.start, newDocument.positionAt(htmlFormattedContent.length - afterFormatRangeLength)); + let embeddedRanges = languageModes.getModesInRange(newDocument, newFormatRange); + let embeddedEdits = []; + for (let r of embeddedRanges) { + let mode = r.mode; + if (mode && mode.format && enabledModes[mode.getId()] && !r.attributeValue) { + let edits = mode.format(newDocument, r, formattingOptions, settings); + for (let edit of edits) { + embeddedEdits.push(edit); + } + } + } + if (embeddedEdits.length === 0) { + arrays_1.pushAll(result, htmlEdits); + return result; + } + // apply all embedded format edits and create a single edit for all changes + let resultContent = languageModes_1.TextDocument.applyEdits(newDocument, embeddedEdits); + let resultReplaceText = resultContent.substring(document.offsetAt(formatRange.start), resultContent.length - afterFormatRangeLength); + result.push(languageModes_1.TextEdit.replace(formatRange, resultReplaceText)); + return result; + } + finally { + languageModes.onDocumentRemoved(newDocument); + } +} +exports.format = format; diff --git a/vscode-html/out/modes/htmlFolding.js b/vscode-html/out/modes/htmlFolding.js new file mode 100644 index 0000000..b99f936 --- /dev/null +++ b/vscode-html/out/modes/htmlFolding.js @@ -0,0 +1,111 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +const languageModes_1 = require("./languageModes"); +function getFoldingRanges(languageModes, document, maxRanges, _cancellationToken) { + let htmlMode = languageModes.getMode('html'); + let range = languageModes_1.Range.create(languageModes_1.Position.create(0, 0), languageModes_1.Position.create(document.lineCount, 0)); + let result = []; + if (htmlMode && htmlMode.getFoldingRanges) { + result.push(...htmlMode.getFoldingRanges(document)); + } + // cache folding ranges per mode + let rangesPerMode = Object.create(null); + let getRangesForMode = (mode) => { + if (mode.getFoldingRanges) { + let ranges = rangesPerMode[mode.getId()]; + if (!Array.isArray(ranges)) { + ranges = mode.getFoldingRanges(document) || []; + rangesPerMode[mode.getId()] = ranges; + } + return ranges; + } + return []; + }; + let modeRanges = languageModes.getModesInRange(document, range); + for (let modeRange of modeRanges) { + let mode = modeRange.mode; + if (mode && mode !== htmlMode && !modeRange.attributeValue) { + const ranges = getRangesForMode(mode); + result.push(...ranges.filter(r => r.startLine >= modeRange.start.line && r.endLine < modeRange.end.line)); + } + } + if (maxRanges && result.length > maxRanges) { + result = limitRanges(result, maxRanges); + } + return result; +} +exports.getFoldingRanges = getFoldingRanges; +function limitRanges(ranges, maxRanges) { + ranges = ranges.sort((r1, r2) => { + let diff = r1.startLine - r2.startLine; + if (diff === 0) { + diff = r1.endLine - r2.endLine; + } + return diff; + }); + // compute each range's nesting level in 'nestingLevels'. + // count the number of ranges for each level in 'nestingLevelCounts' + let top = undefined; + let previous = []; + let nestingLevels = []; + let nestingLevelCounts = []; + let setNestingLevel = (index, level) => { + nestingLevels[index] = level; + if (level < 30) { + nestingLevelCounts[level] = (nestingLevelCounts[level] || 0) + 1; + } + }; + // compute nesting levels and sanitize + for (let i = 0; i < ranges.length; i++) { + let entry = ranges[i]; + if (!top) { + top = entry; + setNestingLevel(i, 0); + } + else { + if (entry.startLine > top.startLine) { + if (entry.endLine <= top.endLine) { + previous.push(top); + top = entry; + setNestingLevel(i, previous.length); + } + else if (entry.startLine > top.endLine) { + do { + top = previous.pop(); + } while (top && entry.startLine > top.endLine); + if (top) { + previous.push(top); + } + top = entry; + setNestingLevel(i, previous.length); + } + } + } + } + let entries = 0; + let maxLevel = 0; + for (let i = 0; i < nestingLevelCounts.length; i++) { + let n = nestingLevelCounts[i]; + if (n) { + if (n + entries > maxRanges) { + maxLevel = i; + break; + } + entries += n; + } + } + let result = []; + for (let i = 0; i < ranges.length; i++) { + let level = nestingLevels[i]; + if (typeof level === 'number') { + if (level < maxLevel || (level === maxLevel && entries++ < maxRanges)) { + result.push(ranges[i]); + } + } + } + return result; +} diff --git a/vscode-html/out/modes/htmlMode.js b/vscode-html/out/modes/htmlMode.js new file mode 100644 index 0000000..241e56e --- /dev/null +++ b/vscode-html/out/modes/htmlMode.js @@ -0,0 +1,96 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +const languageModelCache_1 = require("../languageModelCache"); +const pathCompletion_1 = require("./pathCompletion"); +function getHTMLMode(htmlLanguageService, workspace) { + let htmlDocuments = languageModelCache_1.getLanguageModelCache(10, 60, document => htmlLanguageService.parseHTMLDocument(document)); + return { + getId() { + return 'html'; + }, + getSelectionRange(document, position) { + return htmlLanguageService.getSelectionRanges(document, [position])[0]; + }, + doComplete(document, position, settings = workspace.settings) { + let options = settings && settings.html && settings.html.suggest; + let doAutoComplete = settings && settings.html && settings.html.autoClosingTags; + if (doAutoComplete) { + options.hideAutoCompleteProposals = true; + } + let pathCompletionProposals = []; + let participants = [pathCompletion_1.getPathCompletionParticipant(document, workspace.folders, pathCompletionProposals)]; + htmlLanguageService.setCompletionParticipants(participants); + const htmlDocument = htmlDocuments.get(document); + let completionList = htmlLanguageService.doComplete(document, position, htmlDocument, options); + completionList.items.push(...pathCompletionProposals); + return completionList; + }, + doHover(document, position) { + return htmlLanguageService.doHover(document, position, htmlDocuments.get(document)); + }, + findDocumentHighlight(document, position) { + return htmlLanguageService.findDocumentHighlights(document, position, htmlDocuments.get(document)); + }, + findDocumentLinks(document, documentContext) { + return htmlLanguageService.findDocumentLinks(document, documentContext); + }, + findDocumentSymbols(document) { + return htmlLanguageService.findDocumentSymbols(document, htmlDocuments.get(document)); + }, + format(document, range, formatParams, settings = workspace.settings) { + let formatSettings = settings && settings.html && settings.html.format; + if (formatSettings) { + formatSettings = merge(formatSettings, {}); + } + else { + formatSettings = {}; + } + if (formatSettings.contentUnformatted) { + formatSettings.contentUnformatted = formatSettings.contentUnformatted + ',script'; + } + else { + formatSettings.contentUnformatted = 'script'; + } + formatSettings = merge(formatParams, formatSettings); + return htmlLanguageService.format(document, range, formatSettings); + }, + getFoldingRanges(document) { + return htmlLanguageService.getFoldingRanges(document); + }, + doAutoClose(document, position) { + let offset = document.offsetAt(position); + let text = document.getText(); + if (offset > 0 && text.charAt(offset - 1).match(/[>\/]/g)) { + return htmlLanguageService.doTagComplete(document, position, htmlDocuments.get(document)); + } + return null; + }, + doRename(document, position, newName) { + const htmlDocument = htmlDocuments.get(document); + return htmlLanguageService.doRename(document, position, newName, htmlDocument); + }, + onDocumentRemoved(document) { + htmlDocuments.onDocumentRemoved(document); + }, + findMatchingTagPosition(document, position) { + const htmlDocument = htmlDocuments.get(document); + return htmlLanguageService.findMatchingTagPosition(document, position, htmlDocument); + }, + dispose() { + htmlDocuments.dispose(); + } + }; +} +exports.getHTMLMode = getHTMLMode; +function merge(src, dst) { + for (const key in src) { + if (src.hasOwnProperty(key)) { + dst[key] = src[key]; + } + } + return dst; +} diff --git a/vscode-html/out/modes/javascriptMode.js b/vscode-html/out/modes/javascriptMode.js new file mode 100644 index 0000000..7948b6d --- /dev/null +++ b/vscode-html/out/modes/javascriptMode.js @@ -0,0 +1,429 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +const languageModelCache_1 = require("../languageModelCache"); +const languageModes_1 = require("./languageModes"); +const strings_1 = require("../utils/strings"); +const ts = require("typescript"); +const path_1 = require("path"); +const javascriptSemanticTokens_1 = require("./javascriptSemanticTokens"); +const JS_WORD_REGEX = /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g; +let jquery_d_ts = path_1.join(__dirname, '../lib/jquery.d.ts'); // when packaged +if (!ts.sys.fileExists(jquery_d_ts)) { + jquery_d_ts = path_1.join(__dirname, '../../lib/jquery.d.ts'); // from source +} +function getJavaScriptMode(documentRegions, languageId) { + let jsDocuments = languageModelCache_1.getLanguageModelCache(10, 60, document => documentRegions.get(document).getEmbeddedDocument(languageId)); + const workingFile = languageId === 'javascript' ? 'vscode://javascript/1.js' : 'vscode://javascript/2.ts'; // the same 'file' is used for all contents + let compilerOptions = { allowNonTsExtensions: true, allowJs: true, lib: ['lib.es6.d.ts'], target: ts.ScriptTarget.Latest, moduleResolution: ts.ModuleResolutionKind.Classic }; + let currentTextDocument; + let scriptFileVersion = 0; + function updateCurrentTextDocument(doc) { + if (!currentTextDocument || doc.uri !== currentTextDocument.uri || doc.version !== currentTextDocument.version) { + currentTextDocument = jsDocuments.get(doc); + scriptFileVersion++; + } + } + const host = { + getCompilationSettings: () => compilerOptions, + getScriptFileNames: () => [workingFile, jquery_d_ts], + getScriptKind: (fileName) => fileName.substr(fileName.length - 2) === 'ts' ? ts.ScriptKind.TS : ts.ScriptKind.JS, + getScriptVersion: (fileName) => { + if (fileName === workingFile) { + return String(scriptFileVersion); + } + return '1'; // default lib an jquery.d.ts are static + }, + getScriptSnapshot: (fileName) => { + let text = ''; + if (strings_1.startsWith(fileName, 'vscode:')) { + if (fileName === workingFile) { + text = currentTextDocument.getText(); + } + } + else { + text = ts.sys.readFile(fileName) || ''; + } + return { + getText: (start, end) => text.substring(start, end), + getLength: () => text.length, + getChangeRange: () => undefined + }; + }, + getCurrentDirectory: () => '', + getDefaultLibFileName: (options) => ts.getDefaultLibFilePath(options) + }; + let jsLanguageService = ts.createLanguageService(host); + let globalSettings = {}; + return { + getId() { + return languageId; + }, + doValidation(document) { + updateCurrentTextDocument(document); + const syntaxDiagnostics = jsLanguageService.getSyntacticDiagnostics(workingFile); + const semanticDiagnostics = jsLanguageService.getSemanticDiagnostics(workingFile); + return syntaxDiagnostics.concat(semanticDiagnostics).map((diag) => { + return { + range: convertRange(currentTextDocument, diag), + severity: languageModes_1.DiagnosticSeverity.Error, + source: languageId, + message: ts.flattenDiagnosticMessageText(diag.messageText, '\n') + }; + }); + }, + doComplete(document, position) { + updateCurrentTextDocument(document); + let offset = currentTextDocument.offsetAt(position); + let completions = jsLanguageService.getCompletionsAtPosition(workingFile, offset, { includeExternalModuleExports: false, includeInsertTextCompletions: false }); + if (!completions) { + return { isIncomplete: false, items: [] }; + } + let replaceRange = convertRange(currentTextDocument, strings_1.getWordAtText(currentTextDocument.getText(), offset, JS_WORD_REGEX)); + return { + isIncomplete: false, + items: completions.entries.map(entry => { + return { + uri: document.uri, + position: position, + label: entry.name, + sortText: entry.sortText, + kind: convertKind(entry.kind), + textEdit: languageModes_1.TextEdit.replace(replaceRange, entry.name), + data: { + languageId, + uri: document.uri, + offset: offset + } + }; + }) + }; + }, + doResolve(document, item) { + updateCurrentTextDocument(document); + let details = jsLanguageService.getCompletionEntryDetails(workingFile, item.data.offset, item.label, undefined, undefined, undefined); + if (details) { + item.detail = ts.displayPartsToString(details.displayParts); + item.documentation = ts.displayPartsToString(details.documentation); + delete item.data; + } + return item; + }, + doHover(document, position) { + updateCurrentTextDocument(document); + let info = jsLanguageService.getQuickInfoAtPosition(workingFile, currentTextDocument.offsetAt(position)); + if (info) { + let contents = ts.displayPartsToString(info.displayParts); + return { + range: convertRange(currentTextDocument, info.textSpan), + contents: languageModes_1.MarkedString.fromPlainText(contents) + }; + } + return null; + }, + doSignatureHelp(document, position) { + updateCurrentTextDocument(document); + let signHelp = jsLanguageService.getSignatureHelpItems(workingFile, currentTextDocument.offsetAt(position), undefined); + if (signHelp) { + let ret = { + activeSignature: signHelp.selectedItemIndex, + activeParameter: signHelp.argumentIndex, + signatures: [] + }; + signHelp.items.forEach(item => { + let signature = { + label: '', + documentation: undefined, + parameters: [] + }; + signature.label += ts.displayPartsToString(item.prefixDisplayParts); + item.parameters.forEach((p, i, a) => { + let label = ts.displayPartsToString(p.displayParts); + let parameter = { + label: label, + documentation: ts.displayPartsToString(p.documentation) + }; + signature.label += label; + signature.parameters.push(parameter); + if (i < a.length - 1) { + signature.label += ts.displayPartsToString(item.separatorDisplayParts); + } + }); + signature.label += ts.displayPartsToString(item.suffixDisplayParts); + ret.signatures.push(signature); + }); + return ret; + } + return null; + }, + findDocumentHighlight(document, position) { + updateCurrentTextDocument(document); + const highlights = jsLanguageService.getDocumentHighlights(workingFile, currentTextDocument.offsetAt(position), [workingFile]); + const out = []; + for (const entry of highlights || []) { + for (const highlight of entry.highlightSpans) { + out.push({ + range: convertRange(currentTextDocument, highlight.textSpan), + kind: highlight.kind === 'writtenReference' ? languageModes_1.DocumentHighlightKind.Write : languageModes_1.DocumentHighlightKind.Text + }); + } + } + return out; + }, + findDocumentSymbols(document) { + updateCurrentTextDocument(document); + let items = jsLanguageService.getNavigationBarItems(workingFile); + if (items) { + let result = []; + let existing = Object.create(null); + let collectSymbols = (item, containerLabel) => { + let sig = item.text + item.kind + item.spans[0].start; + if (item.kind !== 'script' && !existing[sig]) { + let symbol = { + name: item.text, + kind: convertSymbolKind(item.kind), + location: { + uri: document.uri, + range: convertRange(currentTextDocument, item.spans[0]) + }, + containerName: containerLabel + }; + existing[sig] = true; + result.push(symbol); + containerLabel = item.text; + } + if (item.childItems && item.childItems.length > 0) { + for (let child of item.childItems) { + collectSymbols(child, containerLabel); + } + } + }; + items.forEach(item => collectSymbols(item)); + return result; + } + return []; + }, + findDefinition(document, position) { + updateCurrentTextDocument(document); + let definition = jsLanguageService.getDefinitionAtPosition(workingFile, currentTextDocument.offsetAt(position)); + if (definition) { + return definition.filter(d => d.fileName === workingFile).map(d => { + return { + uri: document.uri, + range: convertRange(currentTextDocument, d.textSpan) + }; + }); + } + return null; + }, + findReferences(document, position) { + updateCurrentTextDocument(document); + let references = jsLanguageService.getReferencesAtPosition(workingFile, currentTextDocument.offsetAt(position)); + if (references) { + return references.filter(d => d.fileName === workingFile).map(d => { + return { + uri: document.uri, + range: convertRange(currentTextDocument, d.textSpan) + }; + }); + } + return []; + }, + getSelectionRange(document, position) { + updateCurrentTextDocument(document); + function convertSelectionRange(selectionRange) { + const parent = selectionRange.parent ? convertSelectionRange(selectionRange.parent) : undefined; + return languageModes_1.SelectionRange.create(convertRange(currentTextDocument, selectionRange.textSpan), parent); + } + const range = jsLanguageService.getSmartSelectionRange(workingFile, currentTextDocument.offsetAt(position)); + return convertSelectionRange(range); + }, + format(document, range, formatParams, settings = globalSettings) { + currentTextDocument = documentRegions.get(document).getEmbeddedDocument('javascript', true); + scriptFileVersion++; + let formatterSettings = settings && settings.javascript && settings.javascript.format; + let initialIndentLevel = computeInitialIndent(document, range, formatParams); + let formatSettings = convertOptions(formatParams, formatterSettings, initialIndentLevel + 1); + let start = currentTextDocument.offsetAt(range.start); + let end = currentTextDocument.offsetAt(range.end); + let lastLineRange = null; + if (range.end.line > range.start.line && (range.end.character === 0 || strings_1.isWhitespaceOnly(currentTextDocument.getText().substr(end - range.end.character, range.end.character)))) { + end -= range.end.character; + lastLineRange = languageModes_1.Range.create(languageModes_1.Position.create(range.end.line, 0), range.end); + } + let edits = jsLanguageService.getFormattingEditsForRange(workingFile, start, end, formatSettings); + if (edits) { + let result = []; + for (let edit of edits) { + if (edit.span.start >= start && edit.span.start + edit.span.length <= end) { + result.push({ + range: convertRange(currentTextDocument, edit.span), + newText: edit.newText + }); + } + } + if (lastLineRange) { + result.push({ + range: lastLineRange, + newText: generateIndent(initialIndentLevel, formatParams) + }); + } + return result; + } + return []; + }, + getFoldingRanges(document) { + updateCurrentTextDocument(document); + let spans = jsLanguageService.getOutliningSpans(workingFile); + let ranges = []; + for (let span of spans) { + let curr = convertRange(currentTextDocument, span.textSpan); + let startLine = curr.start.line; + let endLine = curr.end.line; + if (startLine < endLine) { + let foldingRange = { startLine, endLine }; + let match = document.getText(curr).match(/^\s*\/(?:(\/\s*#(?:end)?region\b)|(\*|\/))/); + if (match) { + foldingRange.kind = match[1] ? languageModes_1.FoldingRangeKind.Region : languageModes_1.FoldingRangeKind.Comment; + } + ranges.push(foldingRange); + } + } + return ranges; + }, + onDocumentRemoved(document) { + jsDocuments.onDocumentRemoved(document); + }, + getSemanticTokens(document) { + updateCurrentTextDocument(document); + return javascriptSemanticTokens_1.getSemanticTokens(jsLanguageService, currentTextDocument, workingFile); + }, + getSemanticTokenLegend() { + return javascriptSemanticTokens_1.getSemanticTokenLegend(); + }, + dispose() { + jsLanguageService.dispose(); + jsDocuments.dispose(); + } + }; +} +exports.getJavaScriptMode = getJavaScriptMode; +function convertRange(document, span) { + if (typeof span.start === 'undefined') { + const pos = document.positionAt(0); + return languageModes_1.Range.create(pos, pos); + } + const startPosition = document.positionAt(span.start); + const endPosition = document.positionAt(span.start + (span.length || 0)); + return languageModes_1.Range.create(startPosition, endPosition); +} +function convertKind(kind) { + switch (kind) { + case 'primitive type': + case 'keyword': + return languageModes_1.CompletionItemKind.Keyword; + case 'var': + case 'local var': + return languageModes_1.CompletionItemKind.Variable; + case 'property': + case 'getter': + case 'setter': + return languageModes_1.CompletionItemKind.Field; + case 'function': + case 'method': + case 'construct': + case 'call': + case 'index': + return languageModes_1.CompletionItemKind.Function; + case 'enum': + return languageModes_1.CompletionItemKind.Enum; + case 'module': + return languageModes_1.CompletionItemKind.Module; + case 'class': + return languageModes_1.CompletionItemKind.Class; + case 'interface': + return languageModes_1.CompletionItemKind.Interface; + case 'warning': + return languageModes_1.CompletionItemKind.File; + } + return languageModes_1.CompletionItemKind.Property; +} +function convertSymbolKind(kind) { + switch (kind) { + case 'var': + case 'local var': + case 'const': + return languageModes_1.SymbolKind.Variable; + case 'function': + case 'local function': + return languageModes_1.SymbolKind.Function; + case 'enum': + return languageModes_1.SymbolKind.Enum; + case 'module': + return languageModes_1.SymbolKind.Module; + case 'class': + return languageModes_1.SymbolKind.Class; + case 'interface': + return languageModes_1.SymbolKind.Interface; + case 'method': + return languageModes_1.SymbolKind.Method; + case 'property': + case 'getter': + case 'setter': + return languageModes_1.SymbolKind.Property; + } + return languageModes_1.SymbolKind.Variable; +} +function convertOptions(options, formatSettings, initialIndentLevel) { + return { + ConvertTabsToSpaces: options.insertSpaces, + TabSize: options.tabSize, + IndentSize: options.tabSize, + IndentStyle: ts.IndentStyle.Smart, + NewLineCharacter: '\n', + BaseIndentSize: options.tabSize * initialIndentLevel, + InsertSpaceAfterCommaDelimiter: Boolean(!formatSettings || formatSettings.insertSpaceAfterCommaDelimiter), + InsertSpaceAfterSemicolonInForStatements: Boolean(!formatSettings || formatSettings.insertSpaceAfterSemicolonInForStatements), + InsertSpaceBeforeAndAfterBinaryOperators: Boolean(!formatSettings || formatSettings.insertSpaceBeforeAndAfterBinaryOperators), + InsertSpaceAfterKeywordsInControlFlowStatements: Boolean(!formatSettings || formatSettings.insertSpaceAfterKeywordsInControlFlowStatements), + InsertSpaceAfterFunctionKeywordForAnonymousFunctions: Boolean(!formatSettings || formatSettings.insertSpaceAfterFunctionKeywordForAnonymousFunctions), + InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: Boolean(formatSettings && formatSettings.insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis), + InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: Boolean(formatSettings && formatSettings.insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets), + InsertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: Boolean(formatSettings && formatSettings.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces), + InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: Boolean(formatSettings && formatSettings.insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces), + PlaceOpenBraceOnNewLineForControlBlocks: Boolean(formatSettings && formatSettings.placeOpenBraceOnNewLineForFunctions), + PlaceOpenBraceOnNewLineForFunctions: Boolean(formatSettings && formatSettings.placeOpenBraceOnNewLineForControlBlocks) + }; +} +function computeInitialIndent(document, range, options) { + let lineStart = document.offsetAt(languageModes_1.Position.create(range.start.line, 0)); + let content = document.getText(); + let i = lineStart; + let nChars = 0; + let tabSize = options.tabSize || 4; + while (i < content.length) { + let ch = content.charAt(i); + if (ch === ' ') { + nChars++; + } + else if (ch === '\t') { + nChars += tabSize; + } + else { + break; + } + i++; + } + return Math.floor(nChars / tabSize); +} +function generateIndent(level, options) { + if (options.insertSpaces) { + return strings_1.repeat(' ', level * options.tabSize); + } + else { + return strings_1.repeat('\t', level); + } +} diff --git a/vscode-html/out/modes/javascriptSemanticTokens.js b/vscode-html/out/modes/javascriptSemanticTokens.js new file mode 100644 index 0000000..5ed88aa --- /dev/null +++ b/vscode-html/out/modes/javascriptSemanticTokens.js @@ -0,0 +1,130 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +const ts = require("typescript"); +function getSemanticTokenLegend() { + if (tokenTypes.length !== 11 /* _ */) { + console.warn('TokenType has added new entries.'); + } + if (tokenModifiers.length !== 4 /* _ */) { + console.warn('TokenModifier has added new entries.'); + } + return { types: tokenTypes, modifiers: tokenModifiers }; +} +exports.getSemanticTokenLegend = getSemanticTokenLegend; +function getSemanticTokens(jsLanguageService, currentTextDocument, fileName) { + //https://ts-ast-viewer.com/#code/AQ0g2CmAuwGbALzAJwG4BQZQGNwEMBnQ4AQQEYBmYAb2C22zgEtJwATJVTRxgcwD27AQAp8AGmAAjAJS0A9POB8+7NQ168oscAJz5wANXwAnLug2bsJmAFcTAO2XAA1MHyvgu-UdOeWbOw8ViAAvpagocBAA + let resultTokens = []; + const collector = (node, typeIdx, modifierSet) => { + resultTokens.push({ start: currentTextDocument.positionAt(node.getStart()), length: node.getWidth(), typeIdx, modifierSet }); + }; + collectTokens(jsLanguageService, fileName, { start: 0, length: currentTextDocument.getText().length }, collector); + return resultTokens; +} +exports.getSemanticTokens = getSemanticTokens; +function collectTokens(jsLanguageService, fileName, span, collector) { + const program = jsLanguageService.getProgram(); + if (program) { + const typeChecker = program.getTypeChecker(); + function visit(node) { + if (!node || !ts.textSpanIntersectsWith(span, node.pos, node.getFullWidth())) { + return; + } + if (ts.isIdentifier(node)) { + let symbol = typeChecker.getSymbolAtLocation(node); + if (symbol) { + if (symbol.flags & ts.SymbolFlags.Alias) { + symbol = typeChecker.getAliasedSymbol(symbol); + } + let typeIdx = classifySymbol(symbol); + if (typeIdx !== undefined) { + let modifierSet = 0; + if (node.parent) { + const parentTypeIdx = tokenFromDeclarationMapping[node.parent.kind]; + if (parentTypeIdx === typeIdx && node.parent.name === node) { + modifierSet = 1 << 0 /* declaration */; + } + } + const decl = symbol.valueDeclaration; + const modifiers = decl ? ts.getCombinedModifierFlags(decl) : 0; + const nodeFlags = decl ? ts.getCombinedNodeFlags(decl) : 0; + if (modifiers & ts.ModifierFlags.Static) { + modifierSet |= 1 << 1 /* static */; + } + if (modifiers & ts.ModifierFlags.Async) { + modifierSet |= 1 << 2 /* async */; + } + if ((modifiers & ts.ModifierFlags.Readonly) || (nodeFlags & ts.NodeFlags.Const) || (symbol.getFlags() & ts.SymbolFlags.EnumMember)) { + modifierSet |= 1 << 3 /* readonly */; + } + collector(node, typeIdx, modifierSet); + } + } + } + ts.forEachChild(node, visit); + } + const sourceFile = program.getSourceFile(fileName); + if (sourceFile) { + visit(sourceFile); + } + } +} +function classifySymbol(symbol) { + const flags = symbol.getFlags(); + if (flags & ts.SymbolFlags.Class) { + return 0 /* class */; + } + else if (flags & ts.SymbolFlags.Enum) { + return 1 /* enum */; + } + else if (flags & ts.SymbolFlags.TypeAlias) { + return 5 /* type */; + } + else if (flags & ts.SymbolFlags.Type) { + if (flags & ts.SymbolFlags.Interface) { + return 2 /* interface */; + } + if (flags & ts.SymbolFlags.TypeParameter) { + return 4 /* typeParameter */; + } + } + const decl = symbol.valueDeclaration || symbol.declarations && symbol.declarations[0]; + return decl && tokenFromDeclarationMapping[decl.kind]; +} +const tokenTypes = []; +tokenTypes[0 /* class */] = 'class'; +tokenTypes[1 /* enum */] = 'enum'; +tokenTypes[2 /* interface */] = 'interface'; +tokenTypes[3 /* namespace */] = 'namespace'; +tokenTypes[4 /* typeParameter */] = 'typeParameter'; +tokenTypes[5 /* type */] = 'type'; +tokenTypes[6 /* parameter */] = 'parameter'; +tokenTypes[7 /* variable */] = 'variable'; +tokenTypes[8 /* property */] = 'property'; +tokenTypes[9 /* function */] = 'function'; +tokenTypes[10 /* member */] = 'member'; +const tokenModifiers = []; +tokenModifiers[2 /* async */] = 'async'; +tokenModifiers[0 /* declaration */] = 'declaration'; +tokenModifiers[3 /* readonly */] = 'readonly'; +tokenModifiers[1 /* static */] = 'static'; +const tokenFromDeclarationMapping = { + [ts.SyntaxKind.VariableDeclaration]: 7 /* variable */, + [ts.SyntaxKind.Parameter]: 6 /* parameter */, + [ts.SyntaxKind.PropertyDeclaration]: 8 /* property */, + [ts.SyntaxKind.ModuleDeclaration]: 3 /* namespace */, + [ts.SyntaxKind.EnumDeclaration]: 1 /* enum */, + [ts.SyntaxKind.EnumMember]: 8 /* property */, + [ts.SyntaxKind.ClassDeclaration]: 0 /* class */, + [ts.SyntaxKind.MethodDeclaration]: 10 /* member */, + [ts.SyntaxKind.FunctionDeclaration]: 9 /* function */, + [ts.SyntaxKind.MethodSignature]: 10 /* member */, + [ts.SyntaxKind.GetAccessor]: 8 /* property */, + [ts.SyntaxKind.PropertySignature]: 8 /* property */, + [ts.SyntaxKind.InterfaceDeclaration]: 2 /* interface */, + [ts.SyntaxKind.TypeAliasDeclaration]: 5 /* type */, + [ts.SyntaxKind.TypeParameter]: 4 /* typeParameter */ +}; diff --git a/vscode-html/out/modes/languageModes.js b/vscode-html/out/modes/languageModes.js new file mode 100644 index 0000000..77ae8f7 --- /dev/null +++ b/vscode-html/out/modes/languageModes.js @@ -0,0 +1,90 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +function __export(m) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +} +Object.defineProperty(exports, "__esModule", { value: true }); +const vscode_css_languageservice_1 = require("vscode-css-languageservice"); +const vscode_html_languageservice_1 = require("vscode-html-languageservice"); +const languageModelCache_1 = require("../languageModelCache"); +const cssMode_1 = require("./cssMode"); +const embeddedSupport_1 = require("./embeddedSupport"); +const htmlMode_1 = require("./htmlMode"); +const javascriptMode_1 = require("./javascriptMode"); +__export(require("vscode-html-languageservice")); +function getLanguageModes(supportedLanguages, workspace, clientCapabilities, customDataProviders) { + const htmlLanguageService = vscode_html_languageservice_1.getLanguageService({ customDataProviders, clientCapabilities }); + const cssLanguageService = vscode_css_languageservice_1.getCSSLanguageService({ clientCapabilities }); + let documentRegions = languageModelCache_1.getLanguageModelCache(10, 60, document => embeddedSupport_1.getDocumentRegions(htmlLanguageService, document)); + let modelCaches = []; + modelCaches.push(documentRegions); + let modes = Object.create(null); + modes['html'] = htmlMode_1.getHTMLMode(htmlLanguageService, workspace); + if (supportedLanguages['css']) { + modes['css'] = cssMode_1.getCSSMode(cssLanguageService, documentRegions, workspace); + } + if (supportedLanguages['javascript']) { + modes['javascript'] = javascriptMode_1.getJavaScriptMode(documentRegions, 'javascript'); + modes['typescript'] = javascriptMode_1.getJavaScriptMode(documentRegions, 'typescript'); + } + return { + getModeAtPosition(document, position) { + let languageId = documentRegions.get(document).getLanguageAtPosition(position); + if (languageId) { + return modes[languageId]; + } + return undefined; + }, + getModesInRange(document, range) { + 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) { + let result = []; + for (let languageId of documentRegions.get(document).getLanguagesInDocument()) { + let mode = modes[languageId]; + if (mode) { + result.push(mode); + } + } + return result; + }, + getAllModes() { + let result = []; + for (let languageId in modes) { + let mode = modes[languageId]; + if (mode) { + result.push(mode); + } + } + return result; + }, + getMode(languageId) { + return modes[languageId]; + }, + onDocumentRemoved(document) { + modelCaches.forEach(mc => mc.onDocumentRemoved(document)); + for (let mode in modes) { + modes[mode].onDocumentRemoved(document); + } + }, + dispose() { + modelCaches.forEach(mc => mc.dispose()); + modelCaches = []; + for (let mode in modes) { + modes[mode].dispose(); + } + modes = {}; + } + }; +} +exports.getLanguageModes = getLanguageModes; diff --git a/vscode-html/out/modes/pathCompletion.js b/vscode-html/out/modes/pathCompletion.js new file mode 100644 index 0000000..2f8afb0 --- /dev/null +++ b/vscode-html/out/modes/pathCompletion.js @@ -0,0 +1,171 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +const path = require("path"); +const fs = require("fs"); +const vscode_uri_1 = require("vscode-uri"); +const languageModes_1 = require("./languageModes"); +const strings_1 = require("../utils/strings"); +const arrays_1 = require("../utils/arrays"); +function getPathCompletionParticipant(document, workspaceFolders, result) { + return { + onHtmlAttributeValue: ({ tag, attribute, value: valueBeforeCursor, range }) => { + const fullValue = stripQuotes(document.getText(range)); + if (shouldDoPathCompletion(tag, attribute, fullValue)) { + if (workspaceFolders.length === 0) { + return; + } + const workspaceRoot = resolveWorkspaceRoot(document, workspaceFolders); + const paths = providePaths(valueBeforeCursor, vscode_uri_1.URI.parse(document.uri).fsPath, workspaceRoot); + result.push(...paths.map(p => pathToSuggestion(p, valueBeforeCursor, fullValue, range))); + } + } + }; +} +exports.getPathCompletionParticipant = getPathCompletionParticipant; +function stripQuotes(fullValue) { + if (strings_1.startsWith(fullValue, `'`) || strings_1.startsWith(fullValue, `"`)) { + return fullValue.slice(1, -1); + } + else { + return fullValue; + } +} +function shouldDoPathCompletion(tag, attr, value) { + if (strings_1.startsWith(value, 'http') || strings_1.startsWith(value, 'https') || strings_1.startsWith(value, '//')) { + return false; + } + if (PATH_TAG_AND_ATTR[tag]) { + if (typeof PATH_TAG_AND_ATTR[tag] === 'string') { + return PATH_TAG_AND_ATTR[tag] === attr; + } + else { + return arrays_1.contains(PATH_TAG_AND_ATTR[tag], attr); + } + } + return false; +} +/** + * Get a list of path suggestions. Folder suggestions are suffixed with a slash. + */ +function providePaths(valueBeforeCursor, activeDocFsPath, root) { + const lastIndexOfSlash = valueBeforeCursor.lastIndexOf('/'); + const valueBeforeLastSlash = valueBeforeCursor.slice(0, lastIndexOfSlash + 1); + const startsWithSlash = strings_1.startsWith(valueBeforeCursor, '/'); + let parentDir; + if (startsWithSlash) { + if (!root) { + return []; + } + parentDir = path.resolve(root, '.' + valueBeforeLastSlash); + } + else { + parentDir = path.resolve(activeDocFsPath, '..', valueBeforeLastSlash); + } + try { + const paths = fs.readdirSync(parentDir).map(f => { + return isDir(path.resolve(parentDir, f)) + ? f + '/' + : f; + }); + return paths.filter(p => p[0] !== '.'); + } + catch (e) { + return []; + } +} +function isDir(p) { + try { + return fs.statSync(p).isDirectory(); + } + catch (e) { + return false; + } +} +function pathToSuggestion(p, valueBeforeCursor, fullValue, range) { + const isDir = p[p.length - 1] === '/'; + let replaceRange; + const lastIndexOfSlash = valueBeforeCursor.lastIndexOf('/'); + if (lastIndexOfSlash === -1) { + replaceRange = shiftRange(range, 1, -1); + } + else { + // For cases where cursor is in the middle of attribute value, like