Skip to content

Commit

Permalink
Refactor to improve readability
Browse files Browse the repository at this point in the history
Use `await` in `extension.ts`.
Move some functionality out of `extension.ts`.
Move some TypeScript files into subfolders.
Fix `.editorconfig`.

Fixes redhat-developer#418

Signed-off-by: David Thompson <[email protected]>
  • Loading branch information
datho7561 authored and angelozerr committed Feb 25, 2021
1 parent 2dc91a7 commit 728c669
Show file tree
Hide file tree
Showing 23 changed files with 2,006 additions and 2,450 deletions.
10 changes: 5 additions & 5 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
root = true

[*.ts]
[*.js]
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = false
insert_final_newline = false

[*.{ts,js,json,md}]
indent_style = space
indent_size = 2
1 change: 0 additions & 1 deletion gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ gulp.task('build_server', function(done) {
done();
});


function isWin() {
return /^win/.test(process.platform);
}
Expand Down
2,981 changes: 1,222 additions & 1,759 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 0 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,6 @@
"Formatters",
"Snippets"
],
"resolutions": {
"minimist": "^1.2.5",
"yargs-parser": "^19.0.4"
},
"devDependencies": {
"@types/fs-extra": "^8.0.0",
"@types/node": "^10.14.16",
Expand Down
86 changes: 86 additions & 0 deletions src/api/xmlExtensionApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* Interface for APIs exposed from the extension.
*
* @remarks
* A sample code to use these APIs are as following:
* const ext = await vscode.extensions.getExtension('redhat.vscode-xml').activate();
* ext.addXMLCatalogs(...);
* ext.removeXMLCatalogs(...);
* ext.addXMLFileAssociations(...);
* ext.removeXMLFileAssociations(...);
*/
export interface XMLExtensionApi {
/**
* Adds XML Catalogs in addition to the catalogs defined in the settings.json file.
*
* @remarks
* An example is to call this API:
* ```ts
* addXMLCatalogs(['path/to/catalog.xml', 'path/to/anotherCatalog.xml'])
* ```
* @param catalogs - A list of path to XML catalogs
* @returns None
*/
addXMLCatalogs(catalogs: string[]): void;
/**
* Removes XML Catalogs from the extension.
*
* @remarks
* An example is to call this API:
* ```ts
* removeXMLCatalogs(['path/to/catalog.xml', 'path/to/anotherCatalog.xml'])
* ```
* @param catalogs - A list of path to XML catalogs
* @returns None
*/
removeXMLCatalogs(catalogs: string[]): void;
/**
* Adds XML File Associations in addition to the catalogs defined in the settings.json file.
*
* @remarks
* An example is to call this API:
* ```ts
* addXMLFileAssociations([{
* "systemId": "path/to/file.xsd",
* "pattern": "file1.xml"
* },{
* "systemId": "http://www.w3.org/2001/XMLSchema.xsd",
* "pattern": "file2.xml"
* }])
* ```
* @param fileAssociations - A list of file association
* @returns None
*/
addXMLFileAssociations(fileAssociations: XMLFileAssociation[]): void;
/**
* Removes XML File Associations from the extension.
*
* @remarks
* An example is to call this API:
* ```ts
* removeXMLFileAssociations([{
* "systemId": "path/to/file.xsd",
* "pattern": "file1.xml"
* },{
* "systemId": "http://www.w3.org/2001/XMLSchema.xsd",
* "pattern": "file2.xml"
* }])
* ```
* @param fileAssociations - A list of file association
* @returns None
*/
removeXMLFileAssociations(fileAssociations: XMLFileAssociation[]): void;

}

/**
* Interface for the FileAssociation shape.
* @param systemId - The path to a valid XSD.
* @param pattern - The file pattern associated with the XSD.
*
* @returns None
*/
export interface XMLFileAssociation {
systemId: string,
pattern: string;
}
65 changes: 65 additions & 0 deletions src/api/xmlExtensionApiImplementation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { DidChangeConfigurationNotification, LanguageClient } from "vscode-languageclient";
import { ExternalXmlSettings } from "../settings/externalXmlSettings";
import { getXMLSettings, onConfigurationChange } from "../settings/settings";
import { RequirementsData } from "../server/requirements";
import { XMLExtensionApi, XMLFileAssociation } from "./xmlExtensionApi";

/**
* Returns the implementation of the vscode-xml extension API
*
* @param languageClient
* @param logfile
* @param externalXmlSettings
* @param requirementsData
* @return the implementation of the vscode-xml extension API
*/
export function getXmlExtensionApiImplementation(languageClient: LanguageClient, logfile: string, externalXmlSettings: ExternalXmlSettings, requirementsData: RequirementsData): XMLExtensionApi {
return {
// add API set catalogs to internal memory
addXMLCatalogs: (catalogs: string[]) => {
const externalXmlCatalogs = externalXmlSettings.xmlCatalogs;
catalogs.forEach(element => {
if (!externalXmlCatalogs.includes(element)) {
externalXmlCatalogs.push(element);
}
});
languageClient.sendNotification(DidChangeConfigurationNotification.type, { settings: getXMLSettings(requirementsData.java_home, logfile, externalXmlSettings) });
onConfigurationChange();
},
// remove API set catalogs to internal memory
removeXMLCatalogs: (catalogs: string[]) => {
catalogs.forEach(element => {
const externalXmlCatalogs = externalXmlSettings.xmlCatalogs;
if (externalXmlCatalogs.includes(element)) {
const itemIndex = externalXmlCatalogs.indexOf(element);
externalXmlCatalogs.splice(itemIndex, 1);
}
});
languageClient.sendNotification(DidChangeConfigurationNotification.type, { settings: getXMLSettings(requirementsData.java_home, logfile, externalXmlSettings) });
onConfigurationChange();
},
// add API set fileAssociations to internal memory
addXMLFileAssociations: (fileAssociations: XMLFileAssociation[]) => {
const externalfileAssociations = externalXmlSettings.xmlFileAssociations;
fileAssociations.forEach(element => {
if (!externalfileAssociations.some(fileAssociation => fileAssociation.systemId === element.systemId)) {
externalfileAssociations.push(element);
}
});
languageClient.sendNotification(DidChangeConfigurationNotification.type, { settings: getXMLSettings(requirementsData.java_home, logfile, externalXmlSettings) });
onConfigurationChange();
},
// remove API set fileAssociations to internal memory
removeXMLFileAssociations: (fileAssociations: XMLFileAssociation[]) => {
const externalfileAssociations = externalXmlSettings.xmlFileAssociations;
fileAssociations.forEach(element => {
const itemIndex = externalfileAssociations.findIndex(fileAssociation => fileAssociation.systemId === element.systemId) //returns -1 if item not found
if (itemIndex > -1) {
externalfileAssociations.splice(itemIndex, 1);
}
});
languageClient.sendNotification(DidChangeConfigurationNotification.type, { settings: getXMLSettings(requirementsData.java_home, logfile, externalXmlSettings) });
onConfigurationChange();
}
} as XMLExtensionApi;
}
24 changes: 24 additions & 0 deletions src/client/indentation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { IndentAction, LanguageConfiguration } from "vscode";

export function getIndentationRules(): LanguageConfiguration {
return {

// indentationRules referenced from:
// https://github.com/microsoft/vscode/blob/d00558037359acceea329e718036c19625f91a1a/extensions/html-language-features/client/src/htmlMain.ts#L114-L115
indentationRules: {
increaseIndentPattern: /<(?!\?|[^>]*\/>)([-_\.A-Za-z0-9]+)(?=\s|>)\b[^>]*>(?!.*<\/\1>)|<!--(?!.*-->)|\{[^}"']*$/,
decreaseIndentPattern: /^\s*(<\/[-_\.A-Za-z0-9]+\b[^>]*>|-->|\})/
},
onEnterRules: [
{
beforeText: new RegExp(`<([_:\\w][_:\\w-.\\d]*)([^/>]*(?!/)>)[^<]*$`, 'i'),
afterText: /^<\/([_:\w][_:\w-.\d]*)\s*>/i,
action: { indentAction: IndentAction.IndentOutdent }
},
{
beforeText: new RegExp(`<(\\w[\\w\\d]*)([^/>]*(?!/)>)[^<]*$`, 'i'),
action: { indentAction: IndentAction.Indent }
}
]
};
}
File renamed without changes.
168 changes: 168 additions & 0 deletions src/client/xmlClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import { commands, ExtensionContext, extensions, Position, TextDocument, TextEditor, Uri, window, workspace } from 'vscode';
import { Command, ConfigurationParams, ConfigurationRequest, DidChangeConfigurationNotification, Executable, ExecuteCommandParams, LanguageClient, LanguageClientOptions, MessageType, NotificationType, RequestType, RevealOutputChannelOn, TextDocumentPositionParams } from "vscode-languageclient";
import { XMLFileAssociation } from '../api/xmlExtensionApi';
import { CommandConstants } from '../commands/commandConstants';
import { registerCommands } from '../commands/registerCommands';
import { onExtensionChange } from '../plugin';
import { RequirementsData } from "../server/requirements";
import { ExternalXmlSettings } from "../settings/externalXmlSettings";
import { getXMLConfiguration, getXMLSettings, onConfigurationChange, subscribeJDKChangeConfiguration } from "../settings/settings";
import { containsVariableReferenceToCurrentFile } from '../settings/variableSubstitution';
import { activateTagClosing, AutoCloseResult } from './tagClosing';

namespace ExecuteClientCommandRequest {
export const type: RequestType<ExecuteCommandParams, any, void, void> = new RequestType('xml/executeClientCommand');
}

namespace TagCloseRequest {
export const type: RequestType<TextDocumentPositionParams, AutoCloseResult, any, any> = new RequestType('xml/closeTag');
}

interface ActionableMessage {
severity: MessageType;
message: string;
data?: any;
commands?: Command[];
}

namespace ActionableNotification {
export const type = new NotificationType<ActionableMessage, void>('xml/actionableNotification');
}

let languageClient: LanguageClient;

export async function startLanguageClient(context: ExtensionContext, executable: Executable, logfile: string, externalXmlSettings: ExternalXmlSettings, requirementsData: RequirementsData): Promise<LanguageClient> {

const languageClientOptions: LanguageClientOptions = getLanguageClientOptions(logfile, externalXmlSettings, requirementsData);
languageClient = new LanguageClient('xml', 'XML Support', executable, languageClientOptions);
context.subscriptions.push(languageClient.start());
await languageClient.onReady();

// ---

//Detect JDK configuration changes
context.subscriptions.push(subscribeJDKChangeConfiguration());

setupActionableNotificationListener(languageClient);

// Handler for 'xml/executeClientCommand` request message that executes a command on the client
languageClient.onRequest(ExecuteClientCommandRequest.type, async (params: ExecuteCommandParams) => {
return await commands.executeCommand(params.command, ...params.arguments);
});

registerCommands(context, languageClient);

// Setup autoCloseTags
const tagProvider = (document: TextDocument, position: Position) => {
let param = languageClient.code2ProtocolConverter.asTextDocumentPositionParams(document, position);
let text = languageClient.sendRequest(TagCloseRequest.type, param);
return text;
};
context.subscriptions.push(activateTagClosing(tagProvider, { xml: true, xsl: true }, CommandConstants.AUTO_CLOSE_TAGS));

if (extensions.onDidChange) {// Theia doesn't support this API yet
context.subscriptions.push(extensions.onDidChange(() => {
onExtensionChange(extensions.all, getXMLConfiguration().get("extension.jars", []));
}));
}

// Copied from:
// https://github.com/redhat-developer/vscode-java/pull/1081/files
languageClient.onRequest(ConfigurationRequest.type, (params: ConfigurationParams) => {
const result: any[] = [];
const activeEditor: TextEditor | undefined = window.activeTextEditor;
for (const item of params.items) {
if (activeEditor && activeEditor.document.uri.toString() === Uri.parse(item.scopeUri).toString()) {
if (item.section === "xml.format.insertSpaces") {
result.push(activeEditor.options.insertSpaces);
} else if (item.section === "xml.format.tabSize") {
result.push(activeEditor.options.tabSize);
}
} else {
result.push(workspace.getConfiguration(null, Uri.parse(item.scopeUri)).get(item.section));
}
}
return result;
});

// When the current document changes, update variable values that refer to the current file if these variables are referenced,
// and send the updated settings to the server
context.subscriptions.push(window.onDidChangeActiveTextEditor(() => {
if (containsVariableReferenceToCurrentFile(getXMLConfiguration().get('fileAssociations') as XMLFileAssociation[])) {
languageClient.sendNotification(DidChangeConfigurationNotification.type, { settings: getXMLSettings(requirementsData.java_home, logfile, externalXmlSettings) });
onConfigurationChange();
}
}));

return languageClient;
}

function getLanguageClientOptions(logfile: string, externalXmlSettings: ExternalXmlSettings, requirementsData: RequirementsData): LanguageClientOptions {
return {
// Register the server for xml and xsl
documentSelector: [
{ scheme: 'file', language: 'xml' },
{ scheme: 'file', language: 'xsl' },
{ scheme: 'untitled', language: 'xml' },
{ scheme: 'untitled', language: 'xsl' }
],
revealOutputChannelOn: RevealOutputChannelOn.Never,
//wrap with key 'settings' so it can be handled same a DidChangeConfiguration
initializationOptions: {
settings: getXMLSettings(requirementsData.java_home, logfile, externalXmlSettings),
extendedClientCapabilities: {
codeLens: {
codeLensKind: {
valueSet: [
'references'
]
}
},
actionableNotificationSupport: true,
openSettingsCommandSupport: true
}
},
synchronize: {
//preferences starting with these will trigger didChangeConfiguration
configurationSection: ['xml', '[xml]', 'files.trimFinalNewlines', 'files.trimTrailingWhitespace', 'files.insertFinalNewline']
},
middleware: {
workspace: {
didChangeConfiguration: () => {
languageClient.sendNotification(DidChangeConfigurationNotification.type, { settings: getXMLSettings(requirementsData.java_home, logfile, externalXmlSettings) });
onConfigurationChange();
}
}
}
} as LanguageClientOptions;
}

function setupActionableNotificationListener(languageClient: LanguageClient): void {
languageClient.onNotification(ActionableNotification.type, (notification: ActionableMessage) => {
let show = null;
switch (notification.severity) {
case MessageType.Info:
show = window.showInformationMessage;
break;
case MessageType.Warning:
show = window.showWarningMessage;
break;
case MessageType.Error:
show = window.showErrorMessage;
break;
}
if (!show) {
return;
}
const titles: string[] = notification.commands.map(a => a.title);
show(notification.message, ...titles).then((selection) => {
for (const action of notification.commands) {
if (action.title === selection) {
const args: any[] = (action.arguments) ? action.arguments : [];
commands.executeCommand(action.command, ...args);
break;
}
}
});
});
}
2 changes: 1 addition & 1 deletion src/commands.ts → src/commands/commandConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
/**
* Commonly used commands
*/
export namespace Commands {
export namespace CommandConstants {

/**
* Auto close tags
Expand Down
Loading

0 comments on commit 728c669

Please sign in to comment.