Skip to content

Commit

Permalink
Merge pull request #17 from davelopez/add_clean_workflow_command
Browse files Browse the repository at this point in the history
Add clean workflow command
  • Loading branch information
davelopez authored Mar 13, 2022
2 parents fe63946 + 0353cc5 commit 49b5431
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 20 deletions.
28 changes: 28 additions & 0 deletions client/src/commands/cleanWorkflow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { window } from "vscode";
import { CleanWorkflowDocumentParams, CleanWorkflowDocumentRequest } from "../requestsDefinitions";
import { CustomCommand, getCommandFullIdentifier } from "./common";

/**
* Command to 'clean' the selected workflow document.
* It will apply edits to remove the non-workflow logic related parts.
*/
export class CleanWorkflowCommand extends CustomCommand {
public static id = getCommandFullIdentifier("cleanWorkflow");
readonly identifier: string = CleanWorkflowCommand.id;

async execute(args: any[]): Promise<void> {
if (!window.activeTextEditor) {
return;
}
const { document } = window.activeTextEditor;

const params: CleanWorkflowDocumentParams = { uri: this.client.code2ProtocolConverter.asUri(document.uri) };
const result = await this.client.sendRequest(CleanWorkflowDocumentRequest.type, params);
if (!result) {
throw new Error("Cannot clean the requested document. The server returned no result.");
}
if (result.error) {
throw new Error(result.error);
}
}
}
2 changes: 2 additions & 0 deletions client/src/commands/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { SelectForCleanCompareCommand } from "./selectForCleanCompare";
import { PreviewCleanWorkflowCommand } from "./previewCleanWorkflow";
import { CleanWorkflowProvider } from "../providers/cleanWorkflowProvider";
import { GitProvider } from "../providers/git/common";
import { CleanWorkflowCommand } from "./cleanWorkflow";

/**
* Registers all custom commands declared in package.json
Expand All @@ -13,6 +14,7 @@ import { GitProvider } from "../providers/git/common";
*/
export function setupCommands(context: ExtensionContext, client: CommonLanguageClient, gitProvider: GitProvider) {
context.subscriptions.push(new PreviewCleanWorkflowCommand(client).register());
context.subscriptions.push(new CleanWorkflowCommand(client).register());
const selectForCompareProvider = new SelectForCleanCompareCommand(client);
context.subscriptions.push(selectForCompareProvider.register());
context.subscriptions.push(
Expand Down
15 changes: 15 additions & 0 deletions client/src/requestsDefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,18 @@ import { RequestType } from "vscode-languageclient";
// TODO: Move the contents of this file to a shared lib https://github.com/Microsoft/vscode/issues/15829

export namespace LSRequestIdentifiers {
export const CLEAN_WORKFLOW_DOCUMENT = "galaxy-workflows-ls.cleanWorkflowDocument";
export const CLEAN_WORKFLOW_CONTENTS = "galaxy-workflows-ls.cleanWorkflowContents";
}

export interface CleanWorkflowDocumentParams {
uri: string;
}

export interface CleanWorkflowDocumentResult {
error: string;
}

export interface CleanWorkflowContentsParams {
contents: string;
}
Expand All @@ -14,6 +23,12 @@ export interface CleanWorkflowContentsResult {
contents: string;
}

export namespace CleanWorkflowDocumentRequest {
export const type = new RequestType<CleanWorkflowDocumentParams, CleanWorkflowDocumentResult, void>(
LSRequestIdentifiers.CLEAN_WORKFLOW_DOCUMENT
);
}

export namespace CleanWorkflowContentsRequest {
export const type = new RequestType<CleanWorkflowContentsParams, CleanWorkflowContentsResult, void>(
LSRequestIdentifiers.CLEAN_WORKFLOW_CONTENTS
Expand Down
23 changes: 23 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@
"icon": "$(eye)",
"category": "Galaxy Workflows"
},
{
"command": "galaxy-workflows.cleanWorkflow",
"title": "Clean workflow",
"enablement": "resourceLangId == galaxyworkflow",
"icon": "$(edit)",
"category": "Galaxy Workflows"
},
{
"command": "galaxy-workflows.selectForCleanCompare",
"title": "Select workflow for (clean) compare",
Expand All @@ -71,6 +78,10 @@
"command": "galaxy-workflows.previewCleanWorkflow",
"when": "activeEditor"
},
{
"command": "galaxy-workflows.cleanWorkflow",
"when": "activeEditor"
},
{
"command": "galaxy-workflows.selectForCleanCompare",
"when": "false"
Expand Down Expand Up @@ -103,6 +114,18 @@
"group": "3_compare@2",
"when": "config.git.enabled && !git.missing && galaxy-workflows.gitProviderInitialized"
}
],
"editor/context": [
{
"command": "galaxy-workflows.previewCleanWorkflow",
"group": "galaxyworkflow@1",
"when": "resourceLangId == galaxyworkflow"
},
{
"command": "galaxy-workflows.cleanWorkflow",
"group": "galaxyworkflow@2",
"when": "resourceLangId == galaxyworkflow"
}
]
}
},
Expand Down
135 changes: 118 additions & 17 deletions server/src/commands/cleanWorkflow.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,30 @@
import { ApplyWorkspaceEditParams, Range, TextDocumentEdit, TextEdit } from "vscode-languageserver";
import { TextDocument } from "vscode-languageserver-textdocument";
import { ASTNode, PropertyASTNode, WorkflowDocument } from "../languageTypes";
import { GalaxyWorkflowLanguageServer } from "../server";
import { CustomCommand } from "./common";
import { CleanWorkflowContentsRequest, CleanWorkflowContentsResult } from "./requestsDefinitions";
import {
CleanWorkflowContentsParams,
CleanWorkflowContentsRequest,
CleanWorkflowContentsResult,
CleanWorkflowDocumentParams,
CleanWorkflowDocumentRequest,
CleanWorkflowDocumentResult,
} 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.
* 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);
Expand All @@ -16,30 +35,94 @@ export class CleanWorkflowCommand extends CustomCommand {
}

protected listenToRequests(): void {
this.connection.onRequest(CleanWorkflowContentsRequest.type, async (params) => {
const tempDocument = this.createTempWorkflowDocumentWithContents(params.contents);
const workflowDocument = this.languageService.parseWorkflowDocument(tempDocument);
this.connection.onRequest(CleanWorkflowContentsRequest.type, (params) =>
this.onCleanWorkflowContentsRequest(params)
);
this.connection.onRequest(CleanWorkflowDocumentRequest.type, (params) =>
this.onCleanWorkflowDocumentRequest(params)
);
}

/**
* Processes a `CleanWorkflowContentsRequest` by returning the `clean` contents
* of a workflow document given the raw text contents of the workflow document.
* @param params The request parameters containing the raw text contents of the workflow
* @returns The `clean` contents of the workflow document
*/
private async onCleanWorkflowContentsRequest(
params: CleanWorkflowContentsParams
): Promise<CleanWorkflowContentsResult | undefined> {
const tempDocument = this.createTempWorkflowDocumentWithContents(params.contents);
const workflowDocument = this.languageService.parseWorkflowDocument(tempDocument);
if (workflowDocument) {
return await this.cleanWorkflowContentsResult(workflowDocument);
}
return undefined;
}

/**
* Applies the necessary text edits to the workflow document identified by the given URI to
* remove all the properties in the workflow that are unrelated to the essential workflow logic.
* @param params The request parameters containing the URI of the workflow document.
* @returns An error message if something went wrong
*/
private async onCleanWorkflowDocumentRequest(
params: CleanWorkflowDocumentParams
): Promise<CleanWorkflowDocumentResult> {
try {
const workflowDocument = this.workflowDocuments.get(params.uri);
if (workflowDocument) {
return await this.CleanWorkflowContentsResult(workflowDocument);
const edits = this.getTextEditsToCleanWorkflow(workflowDocument);
const editParams: ApplyWorkspaceEditParams = {
label: "Clean workflow",
edit: {
documentChanges: [
TextDocumentEdit.create(
{
uri: params.uri,
version: null,
},
edits
),
],
},
};
this.connection.workspace.applyEdit(editParams);
}
return undefined;
return { error: "" };
} catch (error) {
return { error: String(error) };
}
}

private getTextEditsToCleanWorkflow(workflowDocument: WorkflowDocument): TextEdit[] {
const nodesToRemove = this.getNonEssentialNodes(workflowDocument, CLEANABLE_PROPERTY_NAMES);
const changes: TextEdit[] = [];
nodesToRemove.forEach((node) => {
const range = this.getReplaceRange(workflowDocument.textDocument, node);
changes.push(TextEdit.replace(range, ""));
});
return changes;
}

private createTempWorkflowDocumentWithContents(contents: string) {
return TextDocument.create("temp://temp-workflow", "galaxyworkflow", 0, contents);
}

private async CleanWorkflowContentsResult(workflowDocument: WorkflowDocument): Promise<CleanWorkflowContentsResult> {
const nodesToRemove = this.getNonEssentialNodes(workflowDocument.jsonDocument.root);
private async cleanWorkflowContentsResult(workflowDocument: WorkflowDocument): Promise<CleanWorkflowContentsResult> {
const nodesToRemove = this.getNonEssentialNodes(workflowDocument, CLEANABLE_PROPERTY_NAMES);
const contents = this.getCleanContents(workflowDocument.textDocument.getText(), nodesToRemove.reverse());
const result: CleanWorkflowContentsResult = {
contents: contents,
};
return result;
}

private getNonEssentialNodes(root: ASTNode | undefined): PropertyASTNode[] {
private getNonEssentialNodes(
workflowDocument: WorkflowDocument,
cleanablePropertyNames: Set<string>
): PropertyASTNode[] {
const root = workflowDocument.jsonDocument.root;
if (!root) {
return [];
}
Expand All @@ -57,7 +140,7 @@ export class CleanWorkflowCommand extends CustomCommand {
} else if (node.type === "object") {
node.properties.forEach((property: PropertyASTNode) => {
const key = property.keyNode.value;
if (CLEANABLE_PROPERTY_NAMES.has(key)) {
if (cleanablePropertyNames.has(key)) {
result.push(property);
}
if (property.valueNode) {
Expand All @@ -79,17 +162,35 @@ export class CleanWorkflowCommand extends CustomCommand {
const removeChunks: string[] = [];
let result = documentText;
nodesToRemove.forEach((node) => {
let startPos = node.offset;
let endPos = node.offset + node.length;
startPos = documentText.lastIndexOf("\n", startPos);
if (documentText.charAt(endPos) === ",") {
endPos++;
}
removeChunks.push(documentText.substring(startPos, endPos));
const rangeOffsets = this.getFullNodeRangeOffsets(documentText, node);
removeChunks.push(documentText.substring(rangeOffsets.start, rangeOffsets.end));
});
removeChunks.forEach((chunk) => {
result = result.replace(chunk, "");
});
return result;
}

/**
* Gets the range offsets (`start` and `end`) for a given syntax node including
* the blank spaces/indentation before and after the node and possible ending comma.
* @param documentText The full workflow document text
* @param node The syntax node
* @returns The `start` and `end` offsets for the given syntax node
*/
private getFullNodeRangeOffsets(documentText: string, node: ASTNode) {
let startPos = node.offset;
let endPos = node.offset + node.length;
startPos = documentText.lastIndexOf("\n", startPos);
if (documentText.charAt(endPos) === ",") {
endPos++;
}
return { start: startPos, end: endPos };
}

private getReplaceRange(document: TextDocument, node: ASTNode): Range {
const documentText = document.getText();
const rangeOffsets = this.getFullNodeRangeOffsets(documentText, node);
return Range.create(document.positionAt(rangeOffsets.start), document.positionAt(rangeOffsets.end));
}
}
15 changes: 15 additions & 0 deletions server/src/commands/requestsDefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,18 @@ import { RequestType } from "vscode-languageserver";
// TODO: Move the contents of this file to a shared lib https://github.com/Microsoft/vscode/issues/15829

export namespace LSRequestIdentifiers {
export const CLEAN_WORKFLOW_DOCUMENT = "galaxy-workflows-ls.cleanWorkflowDocument";
export const CLEAN_WORKFLOW_CONTENTS = "galaxy-workflows-ls.cleanWorkflowContents";
}

export interface CleanWorkflowDocumentParams {
uri: string;
}

export interface CleanWorkflowDocumentResult {
error: string;
}

export interface CleanWorkflowContentsParams {
contents: string;
}
Expand All @@ -14,6 +23,12 @@ export interface CleanWorkflowContentsResult {
contents: string;
}

export namespace CleanWorkflowDocumentRequest {
export const type = new RequestType<CleanWorkflowDocumentParams, CleanWorkflowDocumentResult, void>(
LSRequestIdentifiers.CLEAN_WORKFLOW_DOCUMENT
);
}

export namespace CleanWorkflowContentsRequest {
export const type = new RequestType<CleanWorkflowContentsParams, CleanWorkflowContentsResult, void>(
LSRequestIdentifiers.CLEAN_WORKFLOW_CONTENTS
Expand Down
6 changes: 3 additions & 3 deletions server/src/models/workflowDocuments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,17 @@ export class WorkflowDocuments {
}

public addOrReplaceWorkflowDocument(document: WorkflowDocument) {
console.log("registered workflow file: ", document.documentUri);
this._documentsCache.set(document.documentUri, document);
//console.debug("workflow files registered: ", this._documentsCache.size);
}

public removeWorkflowDocument(documentUri: string) {
console.log("unregistered workflow file: ", documentUri);
this._documentsCache.delete(documentUri);
//console.debug("workflow files registered: ", this._documentsCache.size);
}

public dispose() {
this._documentsCache.clear();
console.log("workflow document cache cleared");
//console.debug("workflow document cache cleared");
}
}

0 comments on commit 49b5431

Please sign in to comment.