Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add web support #594

Merged
merged 9 commits into from
Oct 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ node_modules/
out/
dist/
.vscode-test/
.vscode-test-web/
test/testFixture/.vscode
*.vsix
.DS_Store
12 changes: 12 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@
"${workspaceRoot}/test/testFixture"
],
"outFiles": ["${workspaceFolder}/out/test/**/*.js"]
},
{
"name": "Launch Web Extension",
"type": "pwa-extensionHost",
"debugWebWorkerHost": true,
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionDevelopmentKind=web"
],
"outFiles": ["${workspaceRoot}/dist/**/*.js"],
"preLaunchTask": "compile webpack"
}
]
}
8 changes: 8 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@
// use the standard tsc in watch mode problem matcher to find compile problems in the output.
"problemMatcher": "$tsc-watch"
},
{
"label": "compile webpack",
"type": "npm",
"script": "compile",
"group": "build",
"isBackground": true,
"problemMatcher": ["$ts-webpack-watch"]
},
{
"label": "compile test",
// the command is a shell script
Expand Down
2 changes: 2 additions & 0 deletions .vscodeignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ src/**
images/**
node_modules/**
scripts/**
build/**
Jenkinsfile
prettier*
**/*.map
Expand All @@ -16,6 +17,7 @@ vsc-extension-quickstart.md
undefined/**
CONTRIBUTING.md
.vscode-test/**
.vscode-test-web/**
**/**.vsix
**/**.tar.gz
!node_modules/prettier/index.js
Expand Down
10 changes: 10 additions & 0 deletions build/polyfills/yamlFormatter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable @typescript-eslint/no-empty-function */
class YAMLFormatter {
constructor() {}
configure() {}
format() {
return [];
}
}
exports.YAMLFormatter = YAMLFormatter;
15 changes: 12 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"validation"
],
"main": "./dist/extension",
"browser": "./dist/extension-web",
"contributes": {
"languages": [
{
Expand Down Expand Up @@ -205,7 +206,8 @@
"test": "yarn test-compile && sh scripts/e2e.sh",
"vscode:prepublish": "webpack --mode production",
"watch": "webpack --mode development --watch --info-verbosity verbose",
"test-compile": "tsc -p ./"
"test-compile": "tsc -p ./",
"run-in-chromium": "npm run compile && vscode-test-web --browserType=chromium --extensionDevelopmentPath=. ."
},
"devDependencies": {
"@types/chai": "^4.2.12",
Expand All @@ -215,6 +217,7 @@
"@types/sinon": "^9.0.5",
"@types/sinon-chai": "^3.2.5",
"@types/vscode": "^1.52.0",
"@types/webpack": "^4.4.10",
"@typescript-eslint/eslint-plugin": "^4.16.1",
"@typescript-eslint/parser": "^4.16.1",
"chai": "^4.2.0",
Expand All @@ -232,13 +235,19 @@
"typescript": "4.1.2",
"umd-compat-loader": "^2.1.2",
"vscode-test": "^1.4.0",
"@vscode/test-web": "0.0.11",
"webpack": "^5.52.1",
"webpack-cli": "^4.8.0"
"webpack-cli": "^4.8.0",
"path-browserify": "^1.0.1",
"buffer": "^6.0.3",
"util": "^0.12.4",
"url": "^0.11.0",
"process": "^0.11.10"
},
"dependencies": {
"@redhat-developer/vscode-redhat-telemetry": "0.4.2",
"fs-extra": "^9.1.0",
"request-light": "^0.4.0",
"request-light": "^0.5.4",
"vscode-languageclient": "7.0.0",
"vscode-nls": "^3.2.1",
"vscode-uri": "^2.0.3",
Expand Down
76 changes: 41 additions & 35 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,20 @@
*--------------------------------------------------------------------------------------------*/
'use strict';

import * as path from 'path';

import { workspace, ExtensionContext, extensions, window, commands } from 'vscode';
import { workspace, ExtensionContext, extensions, window, commands, Uri } from 'vscode';
import {
LanguageClient,
CommonLanguageClient,
LanguageClientOptions,
ServerOptions,
TransportKind,
NotificationType,
RequestType,
RevealOutputChannelOn,
} from 'vscode-languageclient/node';
} from 'vscode-languageclient';
import { CUSTOM_SCHEMA_REQUEST, CUSTOM_CONTENT_REQUEST, SchemaExtensionAPI } from './schema-extension-api';
import { joinPath } from './paths';
import { getJsonSchemaContent, JSONSchemaDocumentContentProvider } from './json-schema-content-provider';
import { JSONSchemaCache } from './json-schema-cache';
import { getJsonSchemaContent, IJSONSchemaCache, JSONSchemaDocumentContentProvider } from './json-schema-content-provider';
import { getConflictingExtensions, showUninstallConflictsNotification } from './extensionConflicts';
import { getRedHatService } from '@redhat-developer/vscode-redhat-telemetry';
import { TelemetryErrorHandler, TelemetryOutputChannel } from './telemetry';
import { TextDecoder } from 'util';

export interface ISchemaAssociations {
[pattern: string]: string[];
Expand Down Expand Up @@ -65,6 +60,12 @@ namespace VSCodeContentRequest {
export const type: RequestType<string, string, any> = new RequestType('vscode/content');
}

// eslint-disable-next-line @typescript-eslint/no-namespace
namespace FSReadFile {
// eslint-disable-next-line @typescript-eslint/ban-types
export const type: RequestType<string, string, {}> = new RequestType('fs/readFile');
}

// eslint-disable-next-line @typescript-eslint/no-namespace
namespace DynamicCustomSchemaRequestRegistration {
// eslint-disable-next-line @typescript-eslint/ban-types
Expand All @@ -77,28 +78,31 @@ namespace ResultLimitReachedNotification {
export const type: NotificationType<string> = new NotificationType('yaml/resultLimitReached');
}

let client: LanguageClient;
let client: CommonLanguageClient;

const lsName = 'YAML Support';

export async function activate(context: ExtensionContext): Promise<SchemaExtensionAPI> {
// Create Telemetry Service
const telemetry = await (await getRedHatService(context)).getTelemetryService();
telemetry.sendStartupEvent();

// The YAML language server is implemented in node
const serverModule = context.asAbsolutePath(path.join('.', 'dist', 'languageserver.js'));
// The debug options for the server
const debugOptions = { execArgv: ['--nolazy', '--inspect=6009'] };

// If the extension is launched in debug mode then the debug server options are used
// Otherwise the run options are used
const serverOptions: ServerOptions = {
run: { module: serverModule, transport: TransportKind.ipc },
debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions },
};
export type LanguageClientConstructor = (
name: string,
description: string,
clientOptions: LanguageClientOptions
) => CommonLanguageClient;

const telemetryErrorHandler = new TelemetryErrorHandler(telemetry, lsName, 4);
export interface RuntimeEnvironment {
readonly telemetry: TelemetryService;
readonly schemaCache: IJSONSchemaCache;
}

export interface TelemetryService {
send(arg: { name: string; properties?: unknown });
}

export function startClient(
context: ExtensionContext,
newLanguageClient: LanguageClientConstructor,
runtime: RuntimeEnvironment
): SchemaExtensionAPI {
const telemetryErrorHandler = new TelemetryErrorHandler(runtime.telemetry, lsName, 4);
const outputChannel = window.createOutputChannel(lsName);
// Options to control the language client
const clientOptions: LanguageClientOptions = {
Expand All @@ -110,13 +114,12 @@ export async function activate(context: ExtensionContext): Promise<SchemaExtensi
},
revealOutputChannelOn: RevealOutputChannelOn.Never,
errorHandler: telemetryErrorHandler,
outputChannel: new TelemetryOutputChannel(outputChannel, telemetry),
outputChannel: new TelemetryOutputChannel(outputChannel, runtime.telemetry),
};

// Create the language client and start it
client = new LanguageClient('yaml', lsName, serverOptions, clientOptions);
client = newLanguageClient('yaml', lsName, clientOptions);

const schemaCache = new JSONSchemaCache(context.globalStorageUri.fsPath, context.globalState, client.outputChannel);
const disposable = client.start();

const schemaExtensionAPI = new SchemaExtensionAPI(client);
Expand All @@ -127,13 +130,13 @@ export async function activate(context: ExtensionContext): Promise<SchemaExtensi
context.subscriptions.push(
workspace.registerTextDocumentContentProvider(
'json-schema',
new JSONSchemaDocumentContentProvider(schemaCache, schemaExtensionAPI)
new JSONSchemaDocumentContentProvider(runtime.schemaCache, schemaExtensionAPI)
)
);

context.subscriptions.push(
client.onTelemetry((e) => {
telemetry.send(e);
runtime.telemetry.send(e);
})
);

Expand All @@ -159,10 +162,13 @@ export async function activate(context: ExtensionContext): Promise<SchemaExtensi
return schemaExtensionAPI.requestCustomSchemaContent(uri);
});
client.onRequest(VSCodeContentRequest.type, (uri: string) => {
return getJsonSchemaContent(uri, schemaCache);
return getJsonSchemaContent(uri, runtime.schemaCache);
});
client.onRequest(FSReadFile.type, (fsPath: string) => {
return workspace.fs.readFile(Uri.file(fsPath)).then((uint8array) => new TextDecoder().decode(uint8array));
});

telemetry.send({ name: 'yaml.server.initialized' });
runtime.telemetry.send({ name: 'yaml.server.initialized' });
// Adapted from:
// https://github.com/microsoft/vscode/blob/94c9ea46838a9a619aeafb7e8afd1170c967bb55/extensions/json-language-features/client/src/jsonClient.ts#L305-L318
client.onNotification(ResultLimitReachedNotification.type, async (message) => {
Expand Down
10 changes: 6 additions & 4 deletions src/json-schema-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
import * as fs from 'fs-extra';
import * as path from 'path';
import * as crypto from 'crypto';
import { Memento, OutputChannel } from 'vscode';
import { Memento } from 'vscode';
import { logToExtensionOutputChannel } from './extension';
import { IJSONSchemaCache } from './json-schema-content-provider';

const CACHE_DIR = 'schemas_cache';
const CACHE_KEY = 'json-schema-key';
Expand All @@ -20,13 +22,13 @@ interface SchemaCache {
[uri: string]: CacheEntry;
}

export class JSONSchemaCache {
export class JSONSchemaCache implements IJSONSchemaCache {
private readonly cachePath: string;
private readonly cache: SchemaCache;

private isInitialized = false;

constructor(globalStoragePath: string, private memento: Memento, private output: OutputChannel) {
constructor(globalStoragePath: string, private memento: Memento) {
this.cachePath = path.join(globalStoragePath, CACHE_DIR);
this.cache = memento.get(CACHE_KEY, {});
}
Expand Down Expand Up @@ -78,7 +80,7 @@ export class JSONSchemaCache {
await this.memento.update(CACHE_KEY, this.cache);
} catch (err) {
delete this.cache[schemaUri];
this.output.appendLine(err);
logToExtensionOutputChannel(err);
}
}

Expand Down
16 changes: 11 additions & 5 deletions src/json-schema-content-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,16 @@

import { TextDocumentContentProvider, Uri, workspace, window } from 'vscode';
import { xhr, configure as configureHttpRequests, getErrorStatusDescription, XHRResponse } from 'request-light';
import { JSONSchemaCache } from './json-schema-cache';
import { SchemaExtensionAPI } from './schema-extension-api';

export interface IJSONSchemaCache {
getETag(schemaUri: string): string | undefined;
putSchema(schemaUri: string, eTag: string, schemaContent: string): Promise<void>;
getSchema(schemaUri: string): Promise<string | undefined>;
}

export class JSONSchemaDocumentContentProvider implements TextDocumentContentProvider {
constructor(private readonly schemaCache: JSONSchemaCache, private readonly schemaApi: SchemaExtensionAPI) {}
constructor(private readonly schemaCache: IJSONSchemaCache, private readonly schemaApi: SchemaExtensionAPI) {}
async provideTextDocumentContent(uri: Uri): Promise<string> {
if (uri.fragment) {
const origUri = uri.fragment;
Expand Down Expand Up @@ -38,7 +43,7 @@ export class JSONSchemaDocumentContentProvider implements TextDocumentContentPro
}
}

export async function getJsonSchemaContent(uri: string, schemaCache: JSONSchemaCache): Promise<string> {
export async function getJsonSchemaContent(uri: string, schemaCache: IJSONSchemaCache): Promise<string> {
const cachedETag = schemaCache.getETag(uri);

const httpSettings = workspace.getConfiguration('http');
Expand All @@ -51,8 +56,9 @@ export async function getJsonSchemaContent(uri: string, schemaCache: JSONSchemaC
return xhr({ url: uri, followRedirects: 5, headers })
.then(async (response) => {
// cache only if server supports 'etag' header
if (response.headers['etag']) {
await schemaCache.putSchema(uri, response.headers['etag'], response.responseText);
const etag = response.headers['etag'];
if (typeof etag === 'string') {
await schemaCache.putSchema(uri, etag, response.responseText);
}
return response.responseText;
})
Expand Down
44 changes: 44 additions & 0 deletions src/node/yamlClientMain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { ExtensionContext } from 'vscode';
import { startClient, LanguageClientConstructor, RuntimeEnvironment } from '../extension';
import { ServerOptions, TransportKind, LanguageClientOptions, LanguageClient } from 'vscode-languageclient/node';

import { SchemaExtensionAPI } from '../schema-extension-api';

import { getRedHatService } from '@redhat-developer/vscode-redhat-telemetry';
import { JSONSchemaCache } from '../json-schema-cache';

// this method is called when vs code is activated
export async function activate(context: ExtensionContext): Promise<SchemaExtensionAPI> {
// Create Telemetry Service
const telemetry = await (await getRedHatService(context)).getTelemetryService();
telemetry.sendStartupEvent();

// The YAML language server is implemented in node
const serverModule = context.asAbsolutePath('./dist/languageserver.js');

// The debug options for the server
const debugOptions = { execArgv: ['--nolazy', '--inspect=6009'] };

// If the extension is launched in debug mode then the debug server options are used
// Otherwise the run options are used
const serverOptions: ServerOptions = {
run: { module: serverModule, transport: TransportKind.ipc },
debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions },
};

const newLanguageClient: LanguageClientConstructor = (id: string, name: string, clientOptions: LanguageClientOptions) => {
return new LanguageClient(id, name, serverOptions, clientOptions);
};

const runtime: RuntimeEnvironment = {
telemetry,
schemaCache: new JSONSchemaCache(context.globalStorageUri.fsPath, context.globalState),
};

return startClient(context, newLanguageClient, runtime);
}
2 changes: 1 addition & 1 deletion src/schema-extension-api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { URI } from 'vscode-uri';
import { LanguageClient, RequestType } from 'vscode-languageclient/node';
import { CommonLanguageClient as LanguageClient, RequestType } from 'vscode-languageclient/node';
import { workspace } from 'vscode';
import { logToExtensionOutputChannel } from './extension';

Expand Down
Loading