Skip to content

Commit

Permalink
feat: Support configuration in user or workspace settings
Browse files Browse the repository at this point in the history
  • Loading branch information
slavek-kucera authored May 21, 2024
1 parent 73ffd55 commit 610c04c
Show file tree
Hide file tree
Showing 22 changed files with 530 additions and 168 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ Follow the section [External Macro Libraries and COPY Members](#External-Macro-L

The `pgm_conf.json` file can be provided implicitly by another product that supports integration with HLASM Language Support (e.g. Endevor Bridge for Git).

You can also specify your processor group configuration in the Visual Studio Code extension settings in the `hlasm.proc_grps` and `hlasm.pgm_conf` keys. When `proc_grps.json` or `pgm_conf.json` files are present in the workspace, they take precedence over any configuration that is specified in the extension settings.

## Language Features

The HLASM Language Support extension parses and analyzes all parts of a HLASM program including the listing. It resolves all ordinary symbols, variable symbols and checks the validity of most instructions. The extension supports conditional and unconditional branching and can define global and local variable symbols. It can also expand macros and COPY instructions.
Expand Down
1 change: 1 addition & 0 deletions clients/vscode-hlasmplugin/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#### Added
- Validate even-odd register requirements of machine instructions
- Support configuration in user or workspace settings

#### Fixed
- Accept additional URI schemes based on currently opened workspaces
Expand Down
21 changes: 19 additions & 2 deletions clients/vscode-hlasmplugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"multi-root ready"
],
"activationEvents": [
"onStartupFinished"
"onStartupFinished",
"onFileSystem:broadcommfd.hlasm-language-support-configuration"
],
"icon": "resources/logo.png",
"main": "./dist/extension.js",
Expand Down Expand Up @@ -440,6 +441,22 @@
"type": "object",
"title": "HLASM Language Support Configuration",
"properties": {
"hlasm.proc_grps": {
"scope": "resource",
"description": "Processor group configuration",
"$ref": "broadcommfd.hlasm-language-support-configuration:///schema/proc_grps.schema.json",
"default": {
"pgroups": []
}
},
"hlasm.pgm_conf": {
"scope": "resource",
"description": "Program configuration",
"$ref": "broadcommfd.hlasm-language-support-configuration:///schema/pgm_conf.schema.json",
"default": {
"pgms": []
}
},
"hlasm.arguments": {
"type": "array",
"default": [],
Expand All @@ -452,7 +469,7 @@
"type": "boolean",
"default": true,
"description": "Whether or not to send file events to HLASM LS extension (file created, changed or deleted). This can be disabled for performance consideration.",
"deprecationMessage": "Deprected: Language server follows dynamic registration options"
"deprecationMessage": "Deprecated: Language server follows dynamic registration options"
},
"hlasm.continuationHandling": {
"type": "boolean",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,52 +13,121 @@
*/

import * as vscode from 'vscode';
import { ConfigurationNodeDetails, ConfigurationNodes, anyConfigurationNodeExists } from '../configurationNodes';
import { proc_grps_file, pgm_conf_file } from '../constants';
import { ConfigurationNodeDetails, ConfigurationNodes, ConfigurationNodesWithoutWorkspace, SettingsConfigurationNodeDetails, SettingsConfigurationNodeDetailsWithoutWorkspace, anyConfigurationNodeExists } from '../configurationNodes';

function codeActionFactory(create: boolean, filename: string | undefined, args: any[] | undefined): vscode.CodeAction {
const filesDescription: string = filename ? filename + ' configuration file' : 'configuration files';
function filenameFromUri(uri: vscode.Uri): string {
return uri.path.substring(uri.path.lastIndexOf('/') + 1);
}

function createAllConfigurationFiles(ws: vscode.Uri, relativeUri: string, pgroup: string): vscode.CodeAction {
return {
title: 'Create configuration files',
command: {
title: 'Create configuration files',
command: 'extension.hlasm-plugin.createCompleteConfig',
arguments: [ws, relativeUri, pgroup],
},
kind: vscode.CodeActionKind.QuickFix
};
}

function createConfigurationFile(filename: string, ws: vscode.Uri, relativeUri: string | null, pgroup: string | null): vscode.CodeAction {
const filesDescription: string = filename + ' configuration file';
return {
title: 'Create ' + filesDescription,
command: {
title: 'Create ' + filesDescription,
command: 'extension.hlasm-plugin.createCompleteConfig',
arguments: [ws, relativeUri, pgroup],
},
kind: vscode.CodeActionKind.QuickFix
};
}

function modifyConfigurationFile(uri: vscode.Uri): vscode.CodeAction {
const filename = filenameFromUri(uri);
const filesDescription: string = filename + ' configuration file';

return {
title: (create ? 'Create ' : 'Modify ') + filesDescription,
title: 'Modify ' + filesDescription,
command: {
title: (create ? 'Create ' : 'Open ') + filesDescription,
command: create ? 'extension.hlasm-plugin.createCompleteConfig' : 'vscode.open',
arguments: args
title: 'Open ' + filesDescription,
command: 'vscode.open',
arguments: [uri]
},
kind: vscode.CodeActionKind.QuickFix
};
}

function generateProcGrpsCodeAction(procGrps: ConfigurationNodeDetails, wsUri: vscode.Uri): vscode.CodeAction {
return procGrps.exists ? codeActionFactory(false, proc_grps_file, [procGrps.uri]) : codeActionFactory(true, proc_grps_file, [wsUri, null, '']);
function modifySettings(s: SettingsConfigurationNodeDetails | SettingsConfigurationNodeDetailsWithoutWorkspace): vscode.CodeAction {
const title = `Modify settings key '${s.key}'`;
if (s.scope === vscode.ConfigurationTarget.WorkspaceFolder)
return {
title,
command: {
title,
command: 'vscode.open',
arguments: [vscode.Uri.joinPath(s.ws, '.vscode/settings.json')],
},
};
else
return {
title,
command: {
title,
command: s.scope === vscode.ConfigurationTarget.Workspace ? 'workbench.action.openWorkspaceSettingsFile' : 'workbench.action.openSettingsJson',
arguments: [{
revealSetting: {
key: s.key,
edit: true,
}
}],
},
};
}

function generateProcGrpsCodeAction(procGrps: ConfigurationNodeDetails | SettingsConfigurationNodeDetails, wsUri: vscode.Uri): vscode.CodeAction {
if (procGrps.exists)
if ('uri' in procGrps)
return modifyConfigurationFile(procGrps.uri);
else
return modifySettings(procGrps);
else
return createConfigurationFile(filenameFromUri(procGrps.uri), wsUri, null, '');
}

function generatePgmConfCodeAction(configNodes: ConfigurationNodes, wsUri: vscode.Uri, documentRelativeUri: string): vscode.CodeAction {
if (configNodes.pgmConf.exists)
return codeActionFactory(false, pgm_conf_file, [configNodes.pgmConf.uri]);
if (configNodes.pgmConf.exists) {
if ('uri' in configNodes.pgmConf)
return modifyConfigurationFile(configNodes.pgmConf.uri);
else
return modifySettings(configNodes.pgmConf);
}
else {
if (configNodes.bridgeJson.exists || configNodes.ebgFolder.exists) {
// TODO: could we trigger B4G sync?
}
return codeActionFactory(true, pgm_conf_file, [wsUri, documentRelativeUri, null]);
return createConfigurationFile(filenameFromUri(configNodes.pgmConf.uri), wsUri, documentRelativeUri, null);
}
}

export function generateConfigurationFilesCodeActions(suggestProcGrpsChange: boolean, suggestPgmConfChange: boolean, configNodes: ConfigurationNodes, wsUri: vscode.Uri, documentRelativeUri: string): vscode.CodeAction[] {
export function generateConfigurationFilesCodeActions(suggestProcGrpsChange: boolean, suggestPgmConfChange: boolean, config: { nodes: ConfigurationNodes, ws: vscode.Uri, documentRelativeUri: string } | { nodes: ConfigurationNodesWithoutWorkspace }): vscode.CodeAction[] {
if (!suggestProcGrpsChange && !suggestPgmConfChange)
return [];

if (!anyConfigurationNodeExists(configNodes))
return [codeActionFactory(true, undefined, [wsUri, documentRelativeUri, 'GRP1'])];
if (!('ws' in config))
return [modifySettings(config.nodes.procGrps), modifySettings(config.nodes.pgmConf)];

if (!anyConfigurationNodeExists(config.nodes))
return [createAllConfigurationFiles(config.ws, config.documentRelativeUri, 'GRP1')];

const result: vscode.CodeAction[] = [];

if (suggestProcGrpsChange)
result.push(generateProcGrpsCodeAction(configNodes.procGrps, wsUri));
result.push(generateProcGrpsCodeAction(config.nodes.procGrps, config.ws));

if (suggestPgmConfChange)
result.push(generatePgmConfCodeAction(configNodes, wsUri, documentRelativeUri));
result.push(generatePgmConfCodeAction(config.nodes, config.ws, config.documentRelativeUri));

return result;
}
96 changes: 85 additions & 11 deletions clients/vscode-hlasmplugin/src/configurationNodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,103 @@ import * as vscode from 'vscode';
import { uriExists } from './helpers'
import { hlasmplugin_folder, proc_grps_file, pgm_conf_file, bridge_json_file, ebg_folder as ebg_folder } from './constants';

export interface ConfigurationNodeDetails {
uri: vscode.Uri | typeof vscode.Uri;
export type ConfigurationNodeDetails = {
uri: vscode.Uri;
exists: boolean;
}
};

export type SettingsConfigurationNodeDetails = {
ws: vscode.Uri;
scope: vscode.ConfigurationTarget;
key: string;
exists: true;
};

export type SettingsConfigurationNodeDetailsWithoutWorkspace = {
scope: vscode.ConfigurationTarget.Global | vscode.ConfigurationTarget.Workspace;
key: string;
exists: boolean;
};

export type BridgeConfigurationNodeDetails = ConfigurationNodeDetails | {
uri: null;
exists: false;
};

export interface ConfigurationNodes {
procGrps: ConfigurationNodeDetails;
pgmConf: ConfigurationNodeDetails;
bridgeJson: ConfigurationNodeDetails;
export type ConfigurationNodes = {
procGrps: ConfigurationNodeDetails | SettingsConfigurationNodeDetails;
pgmConf: ConfigurationNodeDetails | SettingsConfigurationNodeDetails;
bridgeJson: BridgeConfigurationNodeDetails;
ebgFolder: ConfigurationNodeDetails;
};

export type ConfigurationNodesWithoutWorkspace = {
procGrps: SettingsConfigurationNodeDetailsWithoutWorkspace;
pgmConf: SettingsConfigurationNodeDetailsWithoutWorkspace;
};

function addAlternative(node: ConfigurationNodeDetails, ws: vscode.Uri, key: string): ConfigurationNodeDetails | SettingsConfigurationNodeDetails {
if (node.exists)
return node;

const config = vscode.workspace.getConfiguration('hlasm', ws);
const data = config.get(key);

if (!data || typeof data !== 'object')
return node;

const details = config.inspect(key);
return {
ws,
scope: details?.workspaceFolderValue
? vscode.ConfigurationTarget.WorkspaceFolder
: details?.workspaceValue
? vscode.ConfigurationTarget.Workspace
: vscode.ConfigurationTarget.Global,
key: `hlasm.${key}`,
exists: true,
};
}

function generateConfigurationNodesWithoutWorkspace(): ConfigurationNodesWithoutWorkspace {
const config = vscode.workspace.getConfiguration('hlasm');
const procDetails = config.inspect('hlasm.proc_grps');
const pgmDetails = config.inspect('hlasm.pgm_conf');
const scope = vscode.workspace.workspaceFile ? vscode.ConfigurationTarget.Workspace : vscode.ConfigurationTarget.Global;

return {
procGrps: { scope, key: 'hlasm.proc_grps', exists: !!(procDetails?.globalValue || procDetails?.workspaceValue) },
pgmConf: { scope, key: 'hlasm.pgm_conf', exists: !!(pgmDetails?.globalValue || pgmDetails?.workspaceValue) },
};
}

export async function retrieveConfigurationNodes(workspace: vscode.Uri, documentUri: vscode.Uri | undefined, fs: vscode.FileSystem = vscode.workspace.fs): Promise<ConfigurationNodes> {
export async function retrieveConfigurationNodes(workspace: vscode.Uri, documentUri: vscode.Uri | undefined): Promise<ConfigurationNodes>;
export async function retrieveConfigurationNodes(workspace: vscode.Uri, documentUri: vscode.Uri | undefined, fs: vscode.FileSystem): Promise<ConfigurationNodes>;
export async function retrieveConfigurationNodes(workspace: undefined, documentUri: vscode.Uri | undefined): Promise<ConfigurationNodesWithoutWorkspace>;
export async function retrieveConfigurationNodes(workspace: undefined, documentUri: vscode.Uri | undefined, fs: vscode.FileSystem): Promise<ConfigurationNodesWithoutWorkspace>;
export async function retrieveConfigurationNodes(workspace: vscode.Uri | undefined, documentUri: vscode.Uri | undefined, fs: vscode.FileSystem = vscode.workspace.fs): Promise<ConfigurationNodes | ConfigurationNodesWithoutWorkspace> {
if (!workspace)
return generateConfigurationNodesWithoutWorkspace();

const procGrps = vscode.Uri.joinPath(workspace, hlasmplugin_folder, proc_grps_file);
const pgmConf = vscode.Uri.joinPath(workspace, hlasmplugin_folder, pgm_conf_file);
const bridgeJson = documentUri ? vscode.Uri.joinPath(documentUri, "..", bridge_json_file) : undefined;
const ebgFolder = vscode.Uri.joinPath(workspace, ebg_folder);
return Promise.all([


const results = await Promise.all([
uriExists(procGrps, fs).then(b => { return { uri: procGrps, exists: b }; }),
uriExists(pgmConf, fs).then(b => { return { uri: pgmConf, exists: b }; }),
bridgeJson ? uriExists(bridgeJson, fs).then(b => { return { uri: bridgeJson, exists: b }; }) : { uri: vscode.Uri, exists: false },
bridgeJson ? uriExists(bridgeJson, fs).then(b => { return { uri: bridgeJson, exists: b }; }) : { uri: null, exists: false } as BridgeConfigurationNodeDetails,
uriExists(ebgFolder, fs).then(b => { return { uri: ebgFolder, exists: b }; }),
]).then(arr => { return { procGrps: arr[0], pgmConf: arr[1], bridgeJson: arr[2], ebgFolder: arr[3] }; });
]);

return {
procGrps: addAlternative(results[0], workspace, 'proc_grps'),
pgmConf: addAlternative(results[1], workspace, 'pgm_conf'),
bridgeJson: results[2],
ebgFolder: results[3],
};
}

export function anyConfigurationNodeExists(configNodes: ConfigurationNodes) {
Expand Down
34 changes: 34 additions & 0 deletions clients/vscode-hlasmplugin/src/configurationSchemaFileSystem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2024 Broadcom.
* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Broadcom, Inc. - initial API and implementation
*/

import * as vscode from 'vscode';
import { schemaConfiguration } from './constants';

export function registerConfigurationFileSystemProvider(extensionUri: vscode.Uri) {
const generatePath = (uri: vscode.Uri) => vscode.Uri.joinPath(extensionUri, uri.path);

const emmiter = new vscode.EventEmitter<vscode.FileChangeEvent[]>();
vscode.workspace.registerFileSystemProvider(schemaConfiguration, {
onDidChangeFile: emmiter.event,
watch() { return { dispose: () => { } }; },
stat(uri) { return vscode.workspace.fs.stat(generatePath(uri)); },
readDirectory() { throw Error("not implemented"); },
createDirectory() { throw Error("not implemented"); },
readFile(uri) { return vscode.workspace.fs.readFile(generatePath(uri)); },
writeFile() { throw Error("not implemented"); },
delete() { throw Error("not implemented"); },
rename() { throw Error("not implemented"); },
}, { isReadonly: true, isCaseSensitive: true });

}
8 changes: 5 additions & 3 deletions clients/vscode-hlasmplugin/src/configurationsHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,11 @@ export class ConfigurationsHandler {
return generateConfigurationFilesCodeActions(
!configNodes.procGrps.exists,
!(configNodes.pgmConf.exists || configNodes.bridgeJson.exists || configNodes.ebgFolder.exists),
configNodes,
workspace.uri,
documentRelativeUri)
{
nodes: configNodes,
ws: workspace.uri,
documentRelativeUri
})
.map(x => new vscode.CodeLens(document.lineAt(0).range, x.command));
}
}
1 change: 1 addition & 0 deletions clients/vscode-hlasmplugin/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ export const debugTypeHlasm = 'hlasm';
export const schemeExternalFiles = 'hlasm-external';
export const schemeVirtualFiles = 'hlasm';
export const schemeOutput = 'hlasm-output';
export const schemaConfiguration = 'broadcommfd.hlasm-language-support-configuration';
3 changes: 3 additions & 0 deletions clients/vscode-hlasmplugin/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { registerListingServices } from './hlasmListingServices';
import { MementoKey } from './mementoKeys';
import { registerOutputDocumentContentProvider, showOutputCommand } from './hlasmOutputContentProvider';
import { setServerLogLevel } from './hlasmLogLevel';
import { registerConfigurationFileSystemProvider } from './configurationSchemaFileSystem';

const sleep = (ms: number) => {
return new Promise((resolve) => { setTimeout(resolve, ms) });
Expand Down Expand Up @@ -84,6 +85,8 @@ function whenString(x: any): string | undefined {
* activates the extension
*/
export async function activate(context: vscode.ExtensionContext): Promise<HlasmExtension> {
registerConfigurationFileSystemProvider(context.extensionUri);

const serverVariant = getConfig<ServerVariant>('serverVariant', 'native');
const version = whenString(context.extension.packageJSON?.version);

Expand Down
Loading

0 comments on commit 610c04c

Please sign in to comment.