From 3ed67bc863464ee8a1806d4c402b00bb7f409b24 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 27 May 2020 22:35:37 +0200 Subject: [PATCH] add client/server browser parts --- .../src/browser/cssClientBrowserMain.ts | 15 -- .../client/src/browser/cssClientMain.ts | 43 ++++++ .../client/src/browser/vscodeNlsShim.ts | 46 ++++++ .../extension-browser.webpack.config.js | 31 +++- extensions/css-language-features/package.json | 2 +- .../css-language-features/server/package.json | 1 + .../server/src/browser/cssServerMain.ts | 26 ++++ .../server/src/cssServer.ts | 2 +- .../server/src/test/completion.test.ts | 4 +- scripts/code-web.js | 134 ++++++++++++------ 10 files changed, 242 insertions(+), 62 deletions(-) delete mode 100644 extensions/css-language-features/client/src/browser/cssClientBrowserMain.ts create mode 100644 extensions/css-language-features/client/src/browser/cssClientMain.ts create mode 100644 extensions/css-language-features/client/src/browser/vscodeNlsShim.ts create mode 100644 extensions/css-language-features/server/src/browser/cssServerMain.ts diff --git a/extensions/css-language-features/client/src/browser/cssClientBrowserMain.ts b/extensions/css-language-features/client/src/browser/cssClientBrowserMain.ts deleted file mode 100644 index 18e1d607a52..00000000000 --- a/extensions/css-language-features/client/src/browser/cssClientBrowserMain.ts +++ /dev/null @@ -1,15 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { ExtensionContext, window } from 'vscode'; -//import { startClient } from '../cssClient'; - -// this method is called when vs code is activated -export function activate(_context: ExtensionContext) { - - window.showInformationMessage('cssClientBrowserMain.ts running'); - - //startClient(context, {}); -} diff --git a/extensions/css-language-features/client/src/browser/cssClientMain.ts b/extensions/css-language-features/client/src/browser/cssClientMain.ts new file mode 100644 index 00000000000..294cb9120ae --- /dev/null +++ b/extensions/css-language-features/client/src/browser/cssClientMain.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ExtensionContext, window } from 'vscode'; +import { CommonLanguageClient, LanguageClientOptions, MessageTransports } from 'vscode-languageclient'; +import { startClient, LanguageClientConstructor } from '../cssClient'; +import { BrowserMessageReader, BrowserMessageWriter } from 'vscode-jsonrpc/lib/browser/main'; + +declare const Worker: { + new(stringUrl: string): any; +}; + +class BrowserLanguageClient extends CommonLanguageClient { + + constructor(id: string, name: string, clientOptions: LanguageClientOptions, private worker: any) { + super(id, name, clientOptions); + } + + protected createMessageTransports(_encoding: string): Promise { + const reader = new BrowserMessageReader(this.worker); + const writer = new BrowserMessageWriter(this.worker); + return Promise.resolve({ reader, writer }); + } + +} + +// this method is called when vs code is activated +export function activate(context: ExtensionContext) { + const serverMain = context.asAbsolutePath('server/dist/browser/cssServerMain.js'); + try { + const worker = new Worker(serverMain); + const newLanguageClient: LanguageClientConstructor = (id: string, name: string, clientOptions: LanguageClientOptions) => { + return new BrowserLanguageClient(id, name, clientOptions, worker); + }; + + startClient(context, newLanguageClient, {}); + + } catch (e) { + console.log(e); + } +} diff --git a/extensions/css-language-features/client/src/browser/vscodeNlsShim.ts b/extensions/css-language-features/client/src/browser/vscodeNlsShim.ts new file mode 100644 index 00000000000..b4943fe0ee1 --- /dev/null +++ b/extensions/css-language-features/client/src/browser/vscodeNlsShim.ts @@ -0,0 +1,46 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export interface Options { + locale?: string; + cacheLanguageResolution?: boolean; +} +export interface LocalizeInfo { + key: string; + comment: string[]; +} +export interface LocalizeFunc { + (info: LocalizeInfo, message: string, ...args: any[]): string; + (key: string, message: string, ...args: any[]): string; +} +export interface LoadFunc { + (file?: string): LocalizeFunc; +} + +function format(message: string, args: any[]): string { + let result: string; + + if (args.length === 0) { + result = message; + } else { + result = message.replace(/\{(\d+)\}/g, (match, rest) => { + let index = rest[0]; + return typeof args[index] !== 'undefined' ? args[index] : match; + }); + } + return result; +} + +function localize(_key: string | LocalizeInfo, message: string, ...args: any[]): string { + return format(message, args); +} + +export function loadMessageBundle(_file?: string): LocalizeFunc { + return localize; +} + +export function config(_opt?: Options | string): LoadFunc { + return loadMessageBundle; +} diff --git a/extensions/css-language-features/extension-browser.webpack.config.js b/extensions/css-language-features/extension-browser.webpack.config.js index 30a7fe23413..0cf30ec5dc0 100644 --- a/extensions/css-language-features/extension-browser.webpack.config.js +++ b/extensions/css-language-features/extension-browser.webpack.config.js @@ -9,15 +9,40 @@ const withDefaults = require('../shared.webpack.config'); const path = require('path'); +const webpack = require('webpack'); -module.exports = withDefaults({ +const vscodeNlsReplacement = new webpack.NormalModuleReplacementPlugin( + /vscode\-nls\/lib\/main\.js/, + path.join(__dirname, 'client/out/browser/vscodeNlsShim.js') +); + +const clientConfig = withDefaults({ target: 'webworker', context: path.join(__dirname, 'client'), entry: { - extension: './src/browser/cssClientBrowserMain.ts', + extension: './src/browser/cssClientMain.ts' }, output: { - filename: 'cssClientBrowserMain.js', + filename: 'cssClientMain.js', path: path.join(__dirname, 'client', 'dist', 'browser') } }); +clientConfig.plugins[1] = vscodeNlsReplacement; // replace nls bundler +clientConfig.module.rules[0].use.shift(); // remove nls loader + +const serverConfig = withDefaults({ + target: 'webworker', + context: path.join(__dirname, 'server'), + entry: { + extension: './src/browser/cssServerMain.ts', + }, + output: { + filename: 'cssServerMain.js', + path: path.join(__dirname, 'server', 'dist', 'browser'), + libraryTarget: 'var' + } +}); +serverConfig.plugins[1] = vscodeNlsReplacement; // replace nls bundler +serverConfig.module.rules[0].use.shift(); // remove nls loader + +module.exports = [clientConfig, serverConfig]; diff --git a/extensions/css-language-features/package.json b/extensions/css-language-features/package.json index a02e79ecf6d..da1a88021d3 100644 --- a/extensions/css-language-features/package.json +++ b/extensions/css-language-features/package.json @@ -16,7 +16,7 @@ "onCommand:_css.applyCodeAction" ], "main": "./client/out/node/cssClientMain", - "browser": "./client/dist/browser/cssClientBrowserMain", + "browser": "./client/dist/browser/cssClientMain", "enableProposedApi": true, "scripts": { "compile": "gulp compile-extension:css-language-features-client compile-extension:css-language-features-server", diff --git a/extensions/css-language-features/server/package.json b/extensions/css-language-features/server/package.json index 125a0aa745b..6c56161ec94 100644 --- a/extensions/css-language-features/server/package.json +++ b/extensions/css-language-features/server/package.json @@ -8,6 +8,7 @@ "node": "*" }, "main": "./out/node/cssServerMain", + "browser": "./dist/browser/cssServerMain", "dependencies": { "vscode-css-languageservice": "4.3.0-next.1", "vscode-languageserver": "^6.1.1", diff --git a/extensions/css-language-features/server/src/browser/cssServerMain.ts b/extensions/css-language-features/server/src/browser/cssServerMain.ts new file mode 100644 index 00000000000..06baaed175f --- /dev/null +++ b/extensions/css-language-features/server/src/browser/cssServerMain.ts @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ProtocolConnection, createProtocolConnection, Logger, createConnection, InitializeParams, WatchDog } from 'vscode-languageserver'; +import { startServer } from '../cssServer'; +import { BrowserMessageReader, BrowserMessageWriter } from 'vscode-jsonrpc/lib/browser/main'; + +declare let self: any; + +const messageReader = new BrowserMessageReader(self); +const messageWriter = new BrowserMessageWriter(self); + +const watchDog: WatchDog = { + shutdownReceived: false, + initialize(_params: InitializeParams): void { }, + exit(_code: number): void { } +}; + +const connectionFactory = (logger: Logger): ProtocolConnection => { + return createProtocolConnection(messageReader, messageWriter, logger); +}; +const connection = createConnection(connectionFactory, watchDog); + +startServer(connection, {}); diff --git a/extensions/css-language-features/server/src/cssServer.ts b/extensions/css-language-features/server/src/cssServer.ts index c2666a46c50..42a88c9a5dc 100644 --- a/extensions/css-language-features/server/src/cssServer.ts +++ b/extensions/css-language-features/server/src/cssServer.ts @@ -5,7 +5,7 @@ import { Connection, TextDocuments, InitializeParams, InitializeResult, ServerCapabilities, ConfigurationRequest, WorkspaceFolder, TextDocumentSyncKind, NotificationType -} from 'vscode-languageserver'; +} from 'vscode-languageserver/lib/common/api'; import { URI } from 'vscode-uri'; import { getCSSLanguageService, getSCSSLanguageService, getLESSLanguageService, LanguageSettings, LanguageService, Stylesheet, TextDocument, Position } from 'vscode-css-languageservice'; import { getLanguageModelCache } from './languageModelCache'; diff --git a/extensions/css-language-features/server/src/test/completion.test.ts b/extensions/css-language-features/server/src/test/completion.test.ts index 9db85704e1e..55eb64d9789 100644 --- a/extensions/css-language-features/server/src/test/completion.test.ts +++ b/extensions/css-language-features/server/src/test/completion.test.ts @@ -6,7 +6,7 @@ import 'mocha'; import * as assert from 'assert'; import * as path from 'path'; import { URI } from 'vscode-uri'; -import { TextDocument, CompletionList } from 'vscode-languageserver-types'; +import { TextDocument, CompletionList, TextEdit } from 'vscode-languageserver-types'; import { WorkspaceFolder } from 'vscode-languageserver-protocol'; import { getCSSLanguageService, LanguageServiceOptions, getSCSSLanguageService } from 'vscode-css-languageservice'; import { getNodeFSRequestService } from '../node/nodeFs'; @@ -26,7 +26,7 @@ suite('Completions', () => { assert.equal(matches.length, 1, `${expected.label} should only existing once: Actual: ${completions.items.map(c => c.label).join(', ')}`); let match = matches[0]; - if (expected.resultText && match.textEdit) { + if (expected.resultText && TextEdit.is(match.textEdit)) { assert.equal(TextDocument.applyEdits(document, [match.textEdit]), expected.resultText); } }; diff --git a/scripts/code-web.js b/scripts/code-web.js index 549de9019df..8775f3c757d 100755 --- a/scripts/code-web.js +++ b/scripts/code-web.js @@ -14,8 +14,7 @@ const path = require('path'); const util = require('util'); const opn = require('opn'); const minimist = require('minimist'); - -const webpack = require("webpack"); +const webpack = require('webpack'); const APP_ROOT = path.dirname(__dirname); const EXTENSIONS_ROOT = path.join(APP_ROOT, 'extensions'); @@ -23,6 +22,7 @@ const WEB_MAIN = path.join(APP_ROOT, 'src', 'vs', 'code', 'browser', 'workbench' const args = minimist(process.argv, { boolean: [ + 'watch', 'no-launch', 'help' ], @@ -37,6 +37,7 @@ const args = minimist(process.argv, { if (args.help) { console.log( 'yarn web [options]\n' + + ' --watch Watch extensions that require browser specific builds\n' + ' --no-launch Do not open VSCode web in the browser\n' + ' --scheme Protocol (https or http)\n' + ' --host Remote host\n' + @@ -55,6 +56,83 @@ const SCHEME = args.scheme || process.env.VSCODE_SCHEME || 'http'; const HOST = args.host || 'localhost'; const AUTHORITY = process.env.VSCODE_AUTHORITY || `${HOST}:${PORT}`; +const exists = (path) => util.promisify(fs.exists)(path); +const readFile = (path) => util.promisify(fs.readFile)(path); + +async function initialize() { + const extensionFolders = await util.promisify(fs.readdir)(EXTENSIONS_ROOT); + + const staticExtensions = []; + + const webpackConfigs = []; + + await Promise.all(extensionFolders.map(async extensionFolder => { + const packageJSONPath = path.join(EXTENSIONS_ROOT, extensionFolder, 'package.json'); + if (await exists(packageJSONPath)) { + try { + const packageJSON = JSON.parse((await readFile(packageJSONPath)).toString()); + if (packageJSON.main && !packageJSON.browser) { + return; // unsupported + } + + if (packageJSON.browser) { + packageJSON.main = packageJSON.browser; + const webpackConfigPath = path.join(EXTENSIONS_ROOT, extensionFolder, 'extension-browser.webpack.config.js'); + if ((await exists(webpackConfigPath))) { + const configOrFnOrArray = require(webpackConfigPath); + function addConfig(configOrFn) { + if (typeof configOrFn === 'function') { + webpackConfigs.push(configOrFn({}, {})); + } else { + webpackConfigs.push(configOrFn); + } + } + if (Array.isArray(configOrFnOrArray)) { + configOrFnOrArray.forEach(addConfig); + } else { + addConfig(configOrFnOrArray); + } + } + } + + packageJSON.extensionKind = ['web']; // enable for Web + staticExtensions.push({ + packageJSON, + extensionLocation: { scheme: SCHEME, authority: AUTHORITY, path: `/static-extension/${extensionFolder}` } + }); + } catch (e) { + console.log(e); + } + } + })); + + return new Promise((resolve, reject) => { + if (args.watch) { + webpack(webpackConfigs).watch({}, (err, stats) => { + if (err) { + console.log(err); + reject(); + } else { + console.log(stats.toString()); + resolve(staticExtensions); + } + }); + } else { + webpack(webpackConfigs).run((err, stats) => { + if (err) { + console.log(err); + reject(); + } else { + console.log(stats.toString()); + resolve(staticExtensions); + } + }); + } + }); +} + +const staticExtensionsPromise = initialize(); + const server = http.createServer((req, res) => { const parsedUrl = url.parse(req.url, true); const pathname = parsedUrl.pathname; @@ -141,47 +219,23 @@ function handleStaticExtension(req, res, parsedUrl) { * @param {import('http').ServerResponse} res */ async function handleRoot(req, res) { - const extensionFolders = await util.promisify(fs.readdir)(EXTENSIONS_ROOT); - - const staticExtensions = []; - - const webpackConfigs = []; - - await Promise.all(extensionFolders.map(async extensionFolder => { - try { - const packageJSON = JSON.parse((await util.promisify(fs.readFile)(path.join(EXTENSIONS_ROOT, extensionFolder, 'package.json'))).toString()); - if (packageJSON.main && !packageJSON.browser) { - return; // unsupported - } - if (packageJSON.browser) { - packageJSON.main = packageJSON.browser; - const webpackConfigPath = path.join(EXTENSIONS_ROOT, extensionFolder, 'extension-browser.webpack.config.js'); - if ((await util.promisify(fs.exists)(webpackConfigPath))) { - webpackConfigs.push(require(webpackConfigPath)); - packageJSON.main.replace('/out/', '/dist/'); - } - } - - packageJSON.extensionKind = ['web']; // enable for Web - staticExtensions.push({ - packageJSON, - extensionLocation: { scheme: SCHEME, authority: AUTHORITY, path: `/static-extension/${extensionFolder}` } - }); - } catch (error) { - return null; + const match = req.url && req.url.match(/\?([^#]+)/); + let ghPath; + if (match) { + const qs = new URLSearchParams(match[1]); + ghPath = qs.get('gh'); + if (ghPath && !ghPath.startsWith('/')) { + ghPath = '/' + ghPath; } + } + + const staticExtensions = await staticExtensionsPromise; + const webConfiguration = escapeAttribute(JSON.stringify({ + staticExtensions, folderUri: ghPath + ? { scheme: 'github', authority: 'github.com', path: ghPath } + : { scheme: 'memfs', path: `/sample-folder` } })); - webpack(webpackConfigs).watch({}, (err, stats) => { - if (err) { - console.log(err); - } else { - console.log(stats.toString()); - } - }); - - const webConfiguration = escapeAttribute(JSON.stringify({ staticExtensions, folderUri: { scheme: 'memfs', path: `/sample-folder` }})); - const data = (await util.promisify(fs.readFile)(WEB_MAIN)).toString() .replace('{{WORKBENCH_WEB_CONFIGURATION}}', () => webConfiguration) // use a replace function to avoid that regexp replace patterns ($&, $0, ...) are applied .replace('{{WEBVIEW_ENDPOINT}}', '')