Skip to content

Commit

Permalink
Library Sequences from Workspace (#1539)
Browse files Browse the repository at this point in the history
* Pass along the workspaceID to the SequenceEditor

* Create a Sequence library Type and updated the Adaptation object

* Added workspaces sequences as Library Sequences.

* Using the workspace_id all sequences in a workspace are library sequences.
* Pass this list of library sequences along to the different parts of codemirror etc. linter, autocomplete, SelectedCommand Panel.

* Hook up the linter with library sequences.

* The linter will typechecking the library sequences parameters/arguments

* Added auto completion to Library Sequences

* Update unit test to use arrow functions
  • Loading branch information
goetzrrGit authored Nov 21, 2024
1 parent abf6687 commit 452f1f0
Show file tree
Hide file tree
Showing 13 changed files with 463 additions and 91 deletions.
1 change: 1 addition & 0 deletions src/components/expansion/ExpansionRuns.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@
sequenceOutput={selectedSequence ? JSON.stringify(selectedSequence.expanded_sequence, null, 2) : undefined}
readOnly={true}
title="Sequence - Definition Editor (Read-only)"
workspaceId={null}
{user}
/>
</CssGrid>
30 changes: 26 additions & 4 deletions src/components/sequencing/SequenceEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@
parcelToParameterDictionaries,
userSequenceEditorColumns,
userSequenceEditorColumnsWithFormBuilder,
userSequences,
} from '../../stores/sequencing';
import type { User } from '../../types/app';
import type { IOutputFormat, Parcel } from '../../types/sequencing';
import { setupLanguageSupport } from '../../utilities/codemirror';
import type { IOutputFormat, LibrarySequence, Parcel } from '../../types/sequencing';
import { SeqLanguage, setupLanguageSupport } from '../../utilities/codemirror';
import type { CommandInfoMapper } from '../../utilities/codemirror/commandInfoMapper';
import { seqNHighlightBlock, seqqNBlockHighlighter } from '../../utilities/codemirror/seq-n-highlighter';
import { SeqNCommandInfoMapper } from '../../utilities/codemirror/seq-n-tree-utils';
Expand All @@ -56,6 +57,7 @@
import { inputLinter, outputLinter } from '../../utilities/sequence-editor/extension-points';
import { seqNFormat } from '../../utilities/sequence-editor/sequence-autoindent';
import { sequenceTooltip } from '../../utilities/sequence-editor/sequence-tooltip';
import { parseVariables } from '../../utilities/sequence-editor/to-seq-json';
import { showFailureToast, showSuccessToast } from '../../utilities/toast';
import { tooltip } from '../../utilities/tooltip';
import Menu from '../menus/Menu.svelte';
Expand All @@ -74,6 +76,7 @@
export let sequenceOutput: string = '';
export let title: string = 'Sequence - Definition Editor';
export let user: User | null;
export let workspaceId: number | null;
const dispatch = createEventDispatcher<{
sequence: { input: string; output: string };
Expand All @@ -94,6 +97,7 @@
let commandDictionary: CommandDictionary | null;
let disableCopyAndExport: boolean = true;
let parameterDictionaries: ParameterDictionary[] = [];
let librarySequences: LibrarySequence[] = [];
let commandFormBuilderGrid: string;
let editorOutputDiv: HTMLDivElement;
let editorOutputView: EditorView;
Expand Down Expand Up @@ -164,6 +168,18 @@
}
});
librarySequences = $userSequences
.filter(sequence => sequence.workspace_id === workspaceId && sequence.name !== sequenceName)
.map(sequence => {
const tree = SeqLanguage.parser.parse(sequence.definition);
return {
name: sequence.name,
parameters: parseVariables(tree.topNode, sequence.definition, 'ParameterDeclaration') ?? [],
tree,
workspace_id: sequence.workspace_id,
};
});
if (unparsedCommandDictionary) {
if (sequenceName && isInVmlMode) {
getParsedCommandDictionary(unparsedCommandDictionary, user).then(parsedCommandDictionary => {
Expand Down Expand Up @@ -203,11 +219,17 @@
parsedChannelDictionary,
parsedCommandDictionary,
nonNullParsedParameterDictionaries,
librarySequences,
),
),
),
compartmentSeqLinter.reconfigure(
inputLinter(parsedChannelDictionary, parsedCommandDictionary, nonNullParsedParameterDictionaries),
inputLinter(
parsedChannelDictionary,
parsedCommandDictionary,
nonNullParsedParameterDictionaries,
librarySequences,
),
),
compartmentSeqTooltip.reconfigure(
sequenceTooltip(parsedChannelDictionary, parsedCommandDictionary, nonNullParsedParameterDictionaries),
Expand Down Expand Up @@ -251,7 +273,7 @@
EditorView.lineWrapping,
EditorView.theme({ '.cm-gutter': { 'min-height': `${clientHeightGridRightTop}px` } }),
lintGutter(),
compartmentSeqLanguage.of(setupLanguageSupport($sequenceAdaptation.autoComplete(null, null, []))),
compartmentSeqLanguage.of(setupLanguageSupport($sequenceAdaptation.autoComplete(null, null, [], []))),
compartmentSeqLinter.of(inputLinter()),
compartmentSeqTooltip.of(sequenceTooltip()),
EditorView.updateListener.of(debounce(sequenceUpdateListener, 250)),
Expand Down
1 change: 1 addition & 0 deletions src/components/sequencing/SequenceForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@
title="{mode === 'create' ? 'New' : 'Edit'} Sequence - Definition Editor"
{user}
readOnly={!hasPermission}
workspaceId={selectedWorkspaceId}
on:sequence={onSequenceChange}
on:didChangeModelContent={onDidChangeModelContent}
/>
Expand Down
1 change: 1 addition & 0 deletions src/components/sequencing/Sequences.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
sequenceOutput={selectedSequence?.seq_json}
title="Sequence - Definition Editor (Read-only)"
readOnly={true}
{workspaceId}
{user}
/>
</CssGrid>
11 changes: 10 additions & 1 deletion src/types/sequencing.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import type { CompletionContext, CompletionResult } from '@codemirror/autocomplete';
import type { IndentContext } from '@codemirror/language';
import type { Diagnostic } from '@codemirror/lint';
import type { SyntaxNode } from '@lezer/common';
import type { SyntaxNode, Tree } from '@lezer/common';
import type {
ChannelDictionary as AmpcsChannelDictionary,
CommandDictionary as AmpcsCommandDictionary,
ParameterDictionary as AmpcsParameterDictionary,
} from '@nasa-jpl/aerie-ampcs';
import type { VariableDeclaration } from '@nasa-jpl/seq-json-schema/types';
import type { EditorView } from 'codemirror';
import type { DictionaryTypes } from '../enums/dictionaryTypes';
import type { ArgDelegator } from '../utilities/sequence-editor/extension-points';
Expand Down Expand Up @@ -64,6 +65,7 @@ export interface ISequenceAdaptation {
channelDictionary: AmpcsChannelDictionary | null,
commandDictionary: AmpcsCommandDictionary | null,
parameterDictionaries: AmpcsParameterDictionary[],
librarySequences: LibrarySequence[],
) => (context: CompletionContext) => CompletionResult | null;
autoIndent?: () => (context: IndentContext, pos: number) => number | null | undefined;
globals?: GlobalType[];
Expand Down Expand Up @@ -141,6 +143,13 @@ export type UserSequence = {
workspace_id: number;
};

export type LibrarySequence = {
name: string;
parameters: VariableDeclaration[];
tree: Tree;
workspace_id: number;
};

export type UserSequenceInsertInput = Omit<UserSequence, 'created_at' | 'id' | 'owner' | 'updated_at'>;

export type Workspace = {
Expand Down
26 changes: 21 additions & 5 deletions src/utilities/codemirror/codemirror-utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { VariableDeclaration } from '@nasa-jpl/seq-json-schema/types';
import { describe, expect, it } from 'vitest';
import {
getDefaultVariableArgs,
isHexValue,
isQuoted,
parseNumericArg,
Expand Down Expand Up @@ -90,15 +92,15 @@ describe('quoteEscape', () => {
});
});

describe('parseNumericArg', function () {
it("should parse 'float' and 'numeric' args as floats", function () {
describe('parseNumericArg', () => {
it("should parse 'float' and 'numeric' args as floats", () => {
expect(parseNumericArg('1.23', 'float')).toEqual(1.23);
expect(parseNumericArg('2.34', 'numeric')).toEqual(2.34);
expect(parseNumericArg('bad', 'float')).toEqual(NaN);
// can't parse hex numbers as float
expect(parseNumericArg('0xabc', 'float')).toEqual(0);
});
it("should parse 'integer' and 'unsigned' args as integers", function () {
it("should parse 'integer' and 'unsigned' args as integers", () => {
expect(parseNumericArg('123', 'integer')).toEqual(123);
expect(parseNumericArg('234', 'unsigned')).toEqual(234);
expect(parseNumericArg('234.567', 'integer')).toEqual(234);
Expand All @@ -107,8 +109,8 @@ describe('parseNumericArg', function () {
expect(parseNumericArg('0x1f', 'unsigned')).toEqual(31);
});
});
describe('isHexValue', function () {
it('should correctly identify a hex number string', function () {
describe('isHexValue', () => {
it('should correctly identify a hex number string', () => {
expect(isHexValue('12')).toBe(false);
expect(isHexValue('ff')).toBe(false);
expect(isHexValue('0x99')).toBe(true);
Expand All @@ -117,3 +119,17 @@ describe('isHexValue', function () {
expect(isHexValue('0x12xx')).toBe(false);
});
});
describe('getDefaultVariableArgs', () => {
const mockParameters = [
{ name: 'exampleString', type: 'STRING' },
{ allowable_ranges: [{ min: 1.2 }], type: 'FLOAT' },
{ allowable_ranges: [{ min: 5 }], type: 'INT' },
{ allowable_ranges: [{ min: 7 }], type: 'UINT' },
{ allowable_values: ['VALUE1'], enum_name: 'ExampleEnum', type: 'ENUM' },
{ type: 'INT' },
] as VariableDeclaration[];
it('should return default values for different types', () => {
const result = getDefaultVariableArgs(mockParameters);
expect(result).toEqual(['"exampleString"', 1.2, 5, 7, '"VALUE1"', 0]);
});
});
43 changes: 43 additions & 0 deletions src/utilities/codemirror/codemirror-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type {
FswCommandArgumentUnsigned,
FswCommandArgumentVarString,
} from '@nasa-jpl/aerie-ampcs';
import type { VariableDeclaration } from '@nasa-jpl/seq-json-schema/types';
import type { EditorView } from 'codemirror';
import { fswCommandArgDefault } from '../sequence-editor/command-dictionary';
import type { CommandInfoMapper } from './commandInfoMapper';
Expand Down Expand Up @@ -107,6 +108,48 @@ export function getMissingArgDefs(argInfoArray: ArgTextDef[]): FswCommandArgumen
.map(argInfo => argInfo.argDef);
}

export function getDefaultVariableArgs(parameters: VariableDeclaration[]): string[] {
return parameters.map(parameter => {
switch (parameter.type) {
case 'STRING':
return `"${parameter.name}"`;
case 'FLOAT':
return parameter.allowable_ranges && parameter.allowable_ranges.length > 0
? parameter.allowable_ranges[0].min
: 0;
case 'INT':
case 'UINT':
return parameter.allowable_ranges && parameter.allowable_ranges.length > 0
? parameter.allowable_ranges[0].min
: 0;
case 'ENUM':
return parameter.allowable_values && parameter.allowable_values.length > 0
? `"${parameter.allowable_values[0]}"`
: parameter.enum_name
? `${parameter.enum_name}`
: 'UNKNOWN';
default:
throw Error(`unknown argument type ${parameter.type}`);
}
}) as string[];
}

export function addDefaultVariableArgs(
parameters: VariableDeclaration[],
view: EditorView,
commandNode: SyntaxNode,
commandInfoMapper: CommandInfoMapper,
) {
const insertPosition = commandInfoMapper.getArgumentAppendPosition(commandNode);
if (insertPosition !== undefined) {
const str = commandInfoMapper.formatArgumentArray(getDefaultVariableArgs(parameters), commandNode);
const transaction = view.state.update({
changes: { from: insertPosition, insert: str },
});
view.dispatch(transaction);
}
}

export function isQuoted(s: string): boolean {
return s.startsWith('"') && s.endsWith('"');
}
Expand Down
6 changes: 5 additions & 1 deletion src/utilities/codemirror/seq-n-tree-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,11 @@ export class SeqNCommandInfoMapper implements CommandInfoMapper {
}

getArgumentAppendPosition(commandOrRepeatArgNode: SyntaxNode | null): number | undefined {
if (commandOrRepeatArgNode?.name === RULE_COMMAND) {
if (
commandOrRepeatArgNode?.name === RULE_COMMAND ||
commandOrRepeatArgNode?.name === TOKEN_ACTIVATE ||
commandOrRepeatArgNode?.name === TOKEN_LOAD
) {
const argsNode = commandOrRepeatArgNode.getChild('Args');
const stemNode = commandOrRepeatArgNode.getChild('Stem');
return getFromAndTo([stemNode, argsNode]).to;
Expand Down
5 changes: 3 additions & 2 deletions src/utilities/sequence-editor/extension-points.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from '@nasa-jpl/aerie-ampcs';
import { get } from 'svelte/store';
import { inputFormat, sequenceAdaptation } from '../../stores/sequence-adaptation';
import type { IOutputFormat } from '../../types/sequencing';
import type { IOutputFormat, LibrarySequence } from '../../types/sequencing';
import { seqJsonLinter } from './seq-json-linter';
import { sequenceLinter } from './sequence-linter';

Expand Down Expand Up @@ -81,14 +81,15 @@ export function inputLinter(
channelDictionary: ChannelDictionary | null = null,
commandDictionary: CommandDictionary | null = null,
parameterDictionaries: ParameterDictionary[] = [],
librarySequences: LibrarySequence[] = [],
): Extension {
return linter(view => {
const inputLinter = get(sequenceAdaptation).inputFormat.linter;
const tree = syntaxTree(view.state);
const treeNode = tree.topNode;
let diagnostics: Diagnostic[];

diagnostics = sequenceLinter(view, channelDictionary, commandDictionary, parameterDictionaries);
diagnostics = sequenceLinter(view, channelDictionary, commandDictionary, parameterDictionaries, librarySequences);

if (inputLinter !== undefined && commandDictionary !== null) {
diagnostics = inputLinter(diagnostics, commandDictionary, view, treeNode);
Expand Down
Loading

0 comments on commit 452f1f0

Please sign in to comment.