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

Make cleanable properties user configurable #40

Merged
merged 4 commits into from
Jun 2, 2022
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
21 changes: 21 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,27 @@
"path": "./workflow-languages/syntaxes/yml.tmLanguage.json"
}
],
"configuration": [
{
"title": "Galaxy Workflows",
"properties": {
"galaxyWorkflows.cleaning.cleanableProperties": {
"markdownDescription": "These properties will be removed from the workflow document when *cleaning* (or *clean comparing*) workflows.",
"scope": "resource",
"type": "array",
"items": {
"type": "string"
},
"default": [
"position",
"uuid",
"errors",
"version"
]
}
}
}
],
"commands": [
{
"command": "galaxy-workflows.previewCleanWorkflow",
Expand Down
81 changes: 81 additions & 0 deletions server/src/configService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import {
ClientCapabilities,
Connection,
DidChangeConfigurationNotification,
DidChangeConfigurationParams,
} from "vscode-languageserver";

/** Represents all the available settings of the extension. */
interface ExtensionSettings {
cleaning: CleaningSettings;
}

/** Contains settings for workflow cleaning. */
interface CleaningSettings {
/** A list of property names that will be removed from the workflow document when cleaning. */
cleanableProperties: string[];
}

const defaultSettings: ExtensionSettings = {
cleaning: {
cleanableProperties: ["position", "uuid", "errors", "version"],
},
};

let globalSettings: ExtensionSettings = defaultSettings;

// Cache the settings of all open documents
const documentSettingsCache: Map<string, ExtensionSettings> = new Map();

export class ConfigService {
protected hasConfigurationCapability = false;

constructor(public readonly connection: Connection) {
this.connection.onInitialized(() => this.onInitialized());
this.connection.onDidChangeConfiguration((params) => this.onDidChangeConfiguration(params));
}

public initialize(capabilities: ClientCapabilities): void {
this.hasConfigurationCapability = !!(capabilities.workspace && !!capabilities.workspace.configuration);
}

public async getDocumentSettings(uri: string): Promise<ExtensionSettings> {
if (!this.hasConfigurationCapability) {
return Promise.resolve(globalSettings);
}
let result = documentSettingsCache.get(uri);
if (!result) {
result = await this.connection.workspace.getConfiguration({
scopeUri: uri,
section: "galaxyWorkflows",
});
result = result || globalSettings;
this.addToDocumentConfigCache(uri, result);
}
return result;
}

public onDocumentClose(uri: string): void {
documentSettingsCache.delete(uri);
}

private onInitialized(): void {
if (this.hasConfigurationCapability) {
this.connection.client.register(DidChangeConfigurationNotification.type);
}
}

private onDidChangeConfiguration(params: DidChangeConfigurationParams): void {
if (this.hasConfigurationCapability) {
// Reset all cached document settings
documentSettingsCache.clear();
} else {
globalSettings = <ExtensionSettings>(params.settings.galaxyWorkflows || defaultSettings);
}
}

private addToDocumentConfigCache(uri: string, settings: ExtensionSettings): void {
if (uri.startsWith("temp")) return; // Do not cache config from temp files
documentSettingsCache.set(uri, settings);
}
}
13 changes: 9 additions & 4 deletions server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,34 @@ import {
TextDocuments,
WorkspaceFolder,
} from "vscode-languageserver";
import { CleanWorkflowCommand } from "./commands/cleanWorkflow";
import { CleanWorkflowService } from "./services/cleanWorkflow";
import { WorkflowLanguageService, TextDocument, WorkflowDocument } from "./languageTypes";
import { WorkflowDocuments } from "./models/workflowDocuments";
import { SymbolsProvider } from "./providers/symbolsProvider";
import { FormattingProvider } from "./providers/formattingProvider";
import { HoverProvider } from "./providers/hover/hoverProvider";
// import { DebugHoverContentContributor } from "./providers/hover/debugHoverContentContributor";
import { CompletionProvider } from "./providers/completionProvider";
import { ConfigService } from "./configService";

export class GalaxyWorkflowLanguageServer {
public readonly languageService: WorkflowLanguageService;
public readonly configService: ConfigService;
public readonly documents = new TextDocuments(TextDocument);
public readonly workflowDocuments = new WorkflowDocuments();
protected workspaceFolders: WorkspaceFolder[] | null | undefined;

constructor(public readonly connection: Connection, languageService: WorkflowLanguageService) {
this.languageService = languageService;
this.configService = new ConfigService(connection);
// Track open, change and close text document events
this.trackDocumentChanges(connection);

this.connection.onInitialize((params) => this.initialize(params));

this.registerProviders();

this.registerCommands();
this.registerServices();

this.connection.onShutdown(() => this.cleanup());
}
Expand All @@ -40,6 +43,7 @@ export class GalaxyWorkflowLanguageServer {
}

private async initialize(params: InitializeParams): Promise<InitializeResult> {
this.configService.initialize(params.capabilities);
this.workspaceFolders = params.workspaceFolders;

const capabilities: ServerCapabilities = {
Expand All @@ -66,8 +70,8 @@ export class GalaxyWorkflowLanguageServer {
CompletionProvider.register(this);
}

private registerCommands(): void {
CleanWorkflowCommand.register(this);
private registerServices(): void {
CleanWorkflowService.register(this);
}

private trackDocumentChanges(connection: Connection): void {
Expand All @@ -87,6 +91,7 @@ export class GalaxyWorkflowLanguageServer {

private onDidClose(textDocument: TextDocument): void {
this.workflowDocuments.removeWorkflowDocument(textDocument.uri);
this.configService.onDocumentClose(textDocument.uri);
this.clearValidation(textDocument);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ApplyWorkspaceEditParams, Range, TextDocumentEdit, TextEdit } from "vsc
import { TextDocument } from "vscode-languageserver-textdocument";
import { ASTNode, PropertyASTNode, WorkflowDocument } from "../languageTypes";
import { GalaxyWorkflowLanguageServer } from "../server";
import { CustomCommand } from "./common";
import { ServiceBase } from "./common";
import {
CleanWorkflowContentsParams,
CleanWorkflowContentsRequest,
Expand All @@ -13,21 +13,15 @@ import {
} from "./requestsDefinitions";

/**
* A set of property names that are unrelated to the workflow logic.
* Usually used by other tools like the workflow editor.
*/
const CLEANABLE_PROPERTY_NAMES = new Set(["position", "uuid", "errors", "version"]);

/**
* Command for handling workflow `cleaning` requests.
* Service for handling workflow `cleaning` requests.
* Supports both, direct contents (raw document text), and document uri requests
* for cleaning.
* When requesting with a document uri, the workflow document must be already registered in the server
* as a workflow document.
*/
export class CleanWorkflowCommand extends CustomCommand {
public static register(server: GalaxyWorkflowLanguageServer): CleanWorkflowCommand {
return new CleanWorkflowCommand(server);
export class CleanWorkflowService extends ServiceBase {
public static register(server: GalaxyWorkflowLanguageServer): CleanWorkflowService {
return new CleanWorkflowService(server);
}

constructor(server: GalaxyWorkflowLanguageServer) {
Expand Down Expand Up @@ -72,7 +66,8 @@ export class CleanWorkflowCommand extends CustomCommand {
try {
const workflowDocument = this.workflowDocuments.get(params.uri);
if (workflowDocument) {
const edits = this.getTextEditsToCleanWorkflow(workflowDocument);
const settings = await this.server.configService.getDocumentSettings(workflowDocument.textDocument.uri);
const edits = this.getTextEditsToCleanWorkflow(workflowDocument, settings.cleaning.cleanableProperties);
const editParams: ApplyWorkspaceEditParams = {
label: "Clean workflow",
edit: {
Expand All @@ -95,8 +90,11 @@ export class CleanWorkflowCommand extends CustomCommand {
}
}

private getTextEditsToCleanWorkflow(workflowDocument: WorkflowDocument): TextEdit[] {
const nodesToRemove = this.getNonEssentialNodes(workflowDocument, CLEANABLE_PROPERTY_NAMES);
private getTextEditsToCleanWorkflow(
workflowDocument: WorkflowDocument,
cleanablePropertyNames: string[]
): TextEdit[] {
const nodesToRemove = this.getNonEssentialNodes(workflowDocument, cleanablePropertyNames);
const changes: TextEdit[] = [];
nodesToRemove.forEach((node) => {
const range = this.getFullNodeRange(workflowDocument.textDocument, node);
Expand Down Expand Up @@ -127,15 +125,16 @@ export class CleanWorkflowCommand extends CustomCommand {
}

private async cleanWorkflowContentsResult(workflowDocument: WorkflowDocument): Promise<CleanWorkflowContentsResult> {
const nodesToRemove = this.getNonEssentialNodes(workflowDocument, CLEANABLE_PROPERTY_NAMES);
const settings = await this.server.configService.getDocumentSettings(workflowDocument.textDocument.uri);
const nodesToRemove = this.getNonEssentialNodes(workflowDocument, settings.cleaning.cleanableProperties);
const contents = this.getCleanContents(workflowDocument.textDocument.getText(), nodesToRemove.reverse());
const result: CleanWorkflowContentsResult = {
contents: contents,
};
return result;
}

private getNonEssentialNodes(workflowDocument: WorkflowDocument, cleanablePropertyNames: Set<string>): ASTNode[] {
private getNonEssentialNodes(workflowDocument: WorkflowDocument, cleanablePropertyNames: string[]): ASTNode[] {
const root = workflowDocument.rootNode;
if (!root) {
return [];
Expand All @@ -144,6 +143,7 @@ export class CleanWorkflowCommand extends CustomCommand {
const toVisit: { node: ASTNode }[] = [{ node: root }];
let nextToVisit = 0;

const cleanablePropertyNamesSet = new Set(cleanablePropertyNames);
const collectNonEssentialProperties = (node: ASTNode): void => {
if (node.type === "array") {
node.items.forEach((node) => {
Expand All @@ -154,7 +154,7 @@ export class CleanWorkflowCommand extends CustomCommand {
} else if (node.type === "object") {
node.properties.forEach((property: PropertyASTNode) => {
const key = property.keyNode.value;
if (cleanablePropertyNames.has(key)) {
if (cleanablePropertyNamesSet.has(key)) {
result.push(property);
}
if (property.valueNode) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { ServerContext } from "../languageTypes";
import { GalaxyWorkflowLanguageServer } from "../server";

export abstract class CustomCommand extends ServerContext {
export abstract class ServiceBase extends ServerContext {
constructor(server: GalaxyWorkflowLanguageServer) {
super(server);
this.listenToRequests();
}

/**
* This method should call `this.connection.onRequest` to register
* the proper callback for this command request.
* the proper callback for this service request.
*/
protected abstract listenToRequests(): void;
}