Skip to content

Commit

Permalink
Merge pull request #70 from davelopez/improve_symbols_provider
Browse files Browse the repository at this point in the history
Improve symbols provider
  • Loading branch information
davelopez authored Jun 14, 2024
2 parents b494829 + 1e4e053 commit 80d1ca3
Show file tree
Hide file tree
Showing 22 changed files with 491 additions and 146 deletions.
15 changes: 11 additions & 4 deletions server/gx-workflow-ls-format2/src/inversify.config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { container } from "@gxwf/server-common/src/inversify.config";
import { GxFormat2WorkflowLanguageServiceImpl } from "./languageService";
import { WorkflowTestsLanguageServiceContainerModule } from "@gxwf/workflow-tests-language-service/src/inversify.config";
import { GalaxyWorkflowLanguageServer, WorkflowLanguageService } from "@gxwf/server-common/src/languageTypes";
import {
TYPES as COMMON_TYPES,
GalaxyWorkflowLanguageServer,
SymbolsProvider,
WorkflowLanguageService,
} from "@gxwf/server-common/src/languageTypes";
import { GalaxyWorkflowLanguageServerImpl } from "@gxwf/server-common/src/server";
import { TYPES as COMMON_TYPES } from "@gxwf/server-common/src/languageTypes";
import { WorkflowTestsLanguageServiceContainerModule } from "@gxwf/workflow-tests-language-service/src/inversify.config";
import { YAMLLanguageServiceContainerModule } from "@gxwf/yaml-language-service/src/inversify.config";
import { GxFormat2WorkflowLanguageServiceImpl } from "./languageService";
import { GxFormat2WorkflowSymbolsProvider } from "./services/symbols";

export const TYPES = {
...COMMON_TYPES,
Expand All @@ -23,4 +28,6 @@ container
.to(GalaxyWorkflowLanguageServerImpl)
.inSingletonScope();

container.bind<SymbolsProvider>(TYPES.SymbolsProvider).to(GxFormat2WorkflowSymbolsProvider).inSingletonScope();

export { container };
12 changes: 11 additions & 1 deletion server/gx-workflow-ls-format2/src/languageService.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import {
CompletionList,
Diagnostic,
DocumentSymbol,
FormattingOptions,
Hover,
LanguageService,
LanguageServiceBase,
Position,
Range,
SymbolsProvider,
TYPES,
TextDocument,
TextEdit,
WorkflowValidator,
Expand Down Expand Up @@ -39,7 +42,10 @@ export class GxFormat2WorkflowLanguageServiceImpl
private _completionService: GxFormat2CompletionService;
private _validationServices: WorkflowValidator[];

constructor(@inject(YAML_TYPES.YAMLLanguageService) yamlLanguageService: YAMLLanguageService) {
constructor(
@inject(YAML_TYPES.YAMLLanguageService) yamlLanguageService: YAMLLanguageService,
@inject(TYPES.SymbolsProvider) private symbolsProvider: SymbolsProvider
) {
super(LANGUAGE_ID);
this._schemaLoader = new GalaxyWorkflowFormat2SchemaLoader();
this._yamlLanguageService = yamlLanguageService;
Expand Down Expand Up @@ -79,4 +85,8 @@ export class GxFormat2WorkflowLanguageServiceImpl
}
return diagnostics;
}

public override getSymbols(documentContext: GxFormat2WorkflowDocument): DocumentSymbol[] {
return this.symbolsProvider.getSymbols(documentContext);
}
}
21 changes: 21 additions & 0 deletions server/gx-workflow-ls-format2/src/services/symbols.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { SymbolKind } from "@gxwf/server-common/src/languageTypes";
import { SymbolsProviderBase } from "@gxwf/server-common/src/providers/symbolsProvider";
import { injectable } from "inversify";

@injectable()
export class GxFormat2WorkflowSymbolsProvider extends SymbolsProviderBase {
constructor() {
super();
this.stepContainerNames = new Set(["inputs", "outputs", "steps"]);
}

protected override getSymbolKind(nodeType: string): SymbolKind {
switch (nodeType) {
case "doc":
return SymbolKind.String;
case "path":
return SymbolKind.File;
}
return super.getSymbolKind(nodeType);
}
}
50 changes: 50 additions & 0 deletions server/gx-workflow-ls-format2/tests/integration/symbols.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { DocumentSymbol } from "@gxwf/server-common/src/languageTypes";

import "reflect-metadata";
import { GxFormat2WorkflowSymbolsProvider } from "../../src/services/symbols";
import { createFormat2WorkflowDocument } from "../testHelpers";

describe("Format2 Workflow Symbols Provider", () => {
let provider: GxFormat2WorkflowSymbolsProvider;
beforeAll(() => {
provider = new GxFormat2WorkflowSymbolsProvider();
});

function getSymbols(contents: string): DocumentSymbol[] {
const documentContext = createFormat2WorkflowDocument(contents);
return provider.getSymbols(documentContext);
}

it("should return symbols for a workflow", () => {
const content = `
class: GalaxyWorkflow
inputs:
input_1: data
input_2:
type: File
doc: This is the input 2
the_collection:
type: collection
doc: This is a collection
input_int: integer
text_param:
optional: true
default: text value
restrictOnConnections: true
type: text
`;
const symbols = getSymbols(content);
expect(symbols.length).toBe(2);
const classSymbol = symbols[0];
expect(classSymbol.name).toBe("class");
expect(classSymbol.detail).toBe("GalaxyWorkflow");
const inputsSymbol = symbols[1];
expect(inputsSymbol.name).toBe("inputs");
expect(inputsSymbol.children?.length).toBe(5);
expect(inputsSymbol.children?.at(0)?.name).toBe("input_1");
expect(inputsSymbol.children?.at(1)?.name).toBe("input_2");
expect(inputsSymbol.children?.at(2)?.name).toBe("the_collection");
expect(inputsSymbol.children?.at(3)?.name).toBe("input_int");
expect(inputsSymbol.children?.at(4)?.name).toBe("text_param");
});
});
13 changes: 9 additions & 4 deletions server/gx-workflow-ls-native/src/inversify.config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { container } from "@gxwf/server-common/src/inversify.config";
import { NativeWorkflowLanguageServiceImpl } from "./languageService";
import { WorkflowTestsLanguageServiceContainerModule } from "@gxwf/workflow-tests-language-service/src/inversify.config";
import { GalaxyWorkflowLanguageServer, WorkflowLanguageService } from "@gxwf/server-common/src/languageTypes";
import { GalaxyWorkflowLanguageServer, TYPES, WorkflowLanguageService } from "@gxwf/server-common/src/languageTypes";
import { GalaxyWorkflowLanguageServerImpl } from "@gxwf/server-common/src/server";
import { TYPES } from "@gxwf/server-common/src/languageTypes";
import { WorkflowTestsLanguageServiceContainerModule } from "@gxwf/workflow-tests-language-service/src/inversify.config";
import { YAMLLanguageServiceContainerModule } from "@gxwf/yaml-language-service/src/inversify.config";
import { NativeWorkflowLanguageServiceImpl } from "./languageService";
import { NativeWorkflowSymbolsProvider } from "./services/symbols";

container.load(YAMLLanguageServiceContainerModule);
container.load(WorkflowTestsLanguageServiceContainerModule);
Expand All @@ -19,4 +19,9 @@ container
.to(GalaxyWorkflowLanguageServerImpl)
.inSingletonScope();

container
.bind<NativeWorkflowSymbolsProvider>(TYPES.SymbolsProvider)
.to(NativeWorkflowSymbolsProvider)
.inSingletonScope();

export { container };
33 changes: 20 additions & 13 deletions server/gx-workflow-ls-native/src/languageService.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
import {
DocumentLanguageSettings,
getLanguageService,
JSONSchema,
LanguageService as JSONLanguageService,
LanguageServiceParams,
LanguageSettings,
SchemaConfiguration,
} from "vscode-json-languageservice";
import {
CompletionList,
Diagnostic,
DocumentSymbol,
FormattingOptions,
Hover,
LanguageService,
LanguageServiceBase,
Position,
Range,
SymbolsProvider,
TYPES,
TextDocument,
TextEdit,
LanguageServiceBase,
LanguageService,
} from "@gxwf/server-common/src/languageTypes";
import { inject, injectable } from "inversify";
import {
DocumentLanguageSettings,
LanguageService as JSONLanguageService,
JSONSchema,
LanguageServiceParams,
LanguageSettings,
SchemaConfiguration,
getLanguageService,
} from "vscode-json-languageservice";
import NativeWorkflowSchema from "../../../workflow-languages/schemas/native.schema.json";
import { NativeWorkflowDocument } from "./nativeWorkflowDocument";
import { injectable } from "inversify";

const LANGUAGE_ID = "galaxyworkflow";

Expand All @@ -39,7 +42,7 @@ export class NativeWorkflowLanguageServiceImpl
private _jsonLanguageService: JSONLanguageService;
private _documentSettings: DocumentLanguageSettings = { schemaValidation: "error" };

constructor() {
constructor(@inject(TYPES.SymbolsProvider) private symbolsProvider: SymbolsProvider) {
super(LANGUAGE_ID);
const params: LanguageServiceParams = {};
const settings = this.getLanguageSettings();
Expand Down Expand Up @@ -94,6 +97,10 @@ export class NativeWorkflowLanguageServiceImpl
return schemaValidationResults;
}

public override getSymbols(documentContext: NativeWorkflowDocument): DocumentSymbol[] {
return this.symbolsProvider.getSymbols(documentContext);
}

private getLanguageSettings(): LanguageSettings {
const settings: LanguageSettings = {
schemas: [this.getWorkflowSchemaConfig()],
Expand Down
26 changes: 26 additions & 0 deletions server/gx-workflow-ls-native/src/services/symbols.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { SymbolsProviderBase } from "@gxwf/server-common/src/providers/symbolsProvider";
import { PropertyASTNode } from "@gxwf/yaml-language-service/src/parser/astTypes";
import { injectable } from "inversify";

@injectable()
export class NativeWorkflowSymbolsProvider extends SymbolsProviderBase {
constructor() {
super();
this.symbolNamesToIgnore = new Set([
"a_galaxy_workflow",
"position",
"uuid",
"errors",
"format-version",
"version",
]);
this.stepContainerNames = new Set(["steps"]);
}

protected getSymbolName(property: PropertyASTNode): string {
if (this.isStepProperty(property)) {
return this.getNodeName(property.valueNode) ?? "unnamed";
}
return super.getSymbolName(property);
}
}
43 changes: 43 additions & 0 deletions server/gx-workflow-ls-native/tests/unit/symbols.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { NativeWorkflowSymbolsProvider } from "../../src/services/symbols";
import { createNativeWorkflowDocument } from "../testHelpers";
import { TestWorkflowProvider } from "../testWorkflowProvider";

describe("Native Format Symbols Provider", () => {
let provider: NativeWorkflowSymbolsProvider;

beforeEach(() => {
provider = new NativeWorkflowSymbolsProvider();
});

it("should not provide symbols that must be ignored", () => {
const ignoredSymbols = new Set(["a_galaxy_workflow", "position", "format-version", "version"]);
const wfContent = TestWorkflowProvider.workflows.validation.withThreeSteps;
const wfDocument = createNativeWorkflowDocument(wfContent);
// The ignored nodes exist in the document
ignoredSymbols.forEach((ignoredSymbol) => {
const ignoredSymbolExists = wfContent.includes(ignoredSymbol);
expect(ignoredSymbolExists).toBeTruthy();
});
// but they should not be included in the symbols
const symbols = provider.getSymbols(wfDocument);
expect(symbols).not.toBeNull();
symbols.forEach((symbol) => {
expect(ignoredSymbols.has(symbol.name)).toBeFalsy();
});
});

it("should provide symbols for all steps with names", () => {
const wfContent = TestWorkflowProvider.workflows.validation.withThreeSteps;
const wfDocument = createNativeWorkflowDocument(wfContent);
const symbols = provider.getSymbols(wfDocument);
expect(symbols).not.toBeNull();
const stepsSymbol = symbols.find((symbol) => symbol.name === "steps");
expect(stepsSymbol).toBeDefined();
expect(stepsSymbol?.children).toBeDefined();
expect(stepsSymbol?.children?.length).toBe(3);
const stepNames = ["Input dataset", "Input dataset", "Concatenate datasets"];
stepsSymbol?.children?.forEach((step, i) => {
expect(step.name).toBe(stepNames[i]);
});
});
});
8 changes: 7 additions & 1 deletion server/packages/server-common/src/languageTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,13 +180,18 @@ export interface DocumentContext {
internalDocument: unknown;
}

export interface SymbolsProvider {
getSymbols(documentContext: DocumentContext): DocumentSymbol[];
}

export interface LanguageService<T extends DocumentContext> {
readonly languageId: string;

parseDocument(document: TextDocument): T;
format(document: TextDocument, range: Range, options: FormattingOptions): TextEdit[];
doHover(documentContext: T, position: Position): Promise<Hover | null>;
doComplete(documentContext: T, position: Position): Promise<CompletionList | null>;
getSymbols(documentContext: T): DocumentSymbol[];

/**
* Validates the document and reports all the diagnostics found.
Expand All @@ -204,13 +209,13 @@ export interface LanguageService<T extends DocumentContext> {
@injectable()
export abstract class LanguageServiceBase<T extends DocumentContext> implements LanguageService<T> {
constructor(@unmanaged() public readonly languageId: string) {}

protected server?: GalaxyWorkflowLanguageServer;

public abstract parseDocument(document: TextDocument): T;
public abstract format(document: TextDocument, range: Range, options: FormattingOptions): TextEdit[];
public abstract doHover(documentContext: T, position: Position): Promise<Hover | null>;
public abstract doComplete(documentContext: T, position: Position): Promise<CompletionList | null>;
public abstract getSymbols(documentContext: T): DocumentSymbol[];

/** Performs basic syntax and semantic validation based on the document schema. */
protected abstract doValidation(documentContext: T): Promise<Diagnostic[]>;
Expand Down Expand Up @@ -270,6 +275,7 @@ const TYPES = {
WorkflowTestsLanguageService: Symbol.for("WorkflowTestsLanguageService"),
GalaxyWorkflowLanguageServer: Symbol.for("GalaxyWorkflowLanguageServer"),
WorkflowDataProvider: Symbol.for("WorkflowDataProvider"),
SymbolsProvider: Symbol.for("SymbolsProvider"),
};

export { TYPES };
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import { CompletionList, CompletionParams } from "vscode-languageserver";
import { Provider } from "./provider";
import { GalaxyWorkflowLanguageServer } from "../languageTypes";
import { ServerEventHandler } from "./handler";

export class CompletionProvider extends Provider {
public static register(server: GalaxyWorkflowLanguageServer): CompletionProvider {
return new CompletionProvider(server);
}

export class CompletionHandler extends ServerEventHandler {
constructor(server: GalaxyWorkflowLanguageServer) {
super(server);
this.server.connection.onCompletion(async (params) => this.onCompletion(params));
this.register(this.server.connection.onCompletion(async (params) => this.onCompletion(params)));
}

private async onCompletion(params: CompletionParams): Promise<CompletionList | null> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
import {
FormattingOptions,
TextDocument,
TextEdit,
Position,
Range,
DocumentFormattingParams,
DocumentRangeFormattingParams,
FormattingOptions,
GalaxyWorkflowLanguageServer,
Position,
Range,
TextDocument,
TextEdit,
} from "../languageTypes";
import { Provider } from "./provider";

export class FormattingProvider extends Provider {
public static register(server: GalaxyWorkflowLanguageServer): FormattingProvider {
return new FormattingProvider(server);
}
import { ServerEventHandler } from "./handler";

export class FormattingHandler extends ServerEventHandler {
constructor(server: GalaxyWorkflowLanguageServer) {
super(server);
this.server.connection.onDocumentFormatting((params) => this.onDocumentFormatting(params));
this.server.connection.onDocumentRangeFormatting((params) => this.onDocumentRangeFormatting(params));
this.register(this.server.connection.onDocumentFormatting((params) => this.onDocumentFormatting(params)));
this.register(this.server.connection.onDocumentRangeFormatting((params) => this.onDocumentRangeFormatting(params)));
}

public onDocumentFormatting(params: DocumentFormattingParams): TextEdit[] {
Expand Down
Loading

0 comments on commit 80d1ca3

Please sign in to comment.