Skip to content
This repository has been archived by the owner on Oct 23, 2024. It is now read-only.

Commit

Permalink
add web support (redhat-developer#594)
Browse files Browse the repository at this point in the history
* add web support

* fixes

* Avoid NodeJS.Timeout

* Update test/json-schema-cache.test.ts

Co-authored-by: Yevhen Vydolob <[email protected]>

* Update test/json-schema-cache.test.ts

Co-authored-by: Yevhen Vydolob <[email protected]>

* fix merge issues

Co-authored-by: Yevhen Vydolob <[email protected]>
  • Loading branch information
aeschli and evidolob authored Oct 18, 2021
1 parent e06377d commit bf13be3
Show file tree
Hide file tree
Showing 17 changed files with 1,387 additions and 85 deletions.
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

0 comments on commit bf13be3

Please sign in to comment.