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

Show variables and globals in form view #1536

Merged
merged 18 commits into from
Nov 9, 2024
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
2 changes: 1 addition & 1 deletion e2e-tests/fixtures/Sequence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export class Sequence {
await expect(this.command).toHaveText('C FSW_CMD_0 "ON" false 1');
await this.page
.locator('fieldset')
.filter({ hasText: 'enum_arg_0 ONOFF' })
.filter({ hasText: 'enum_arg_0 Value Type Literal' })
.getByRole('combobox')
.selectOption('OFF');

Expand Down
7 changes: 5 additions & 2 deletions src/components/sequencing/SequenceEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import { lintGutter } from '@codemirror/lint';
import { Compartment, EditorState } from '@codemirror/state';
import { type ViewUpdate } from '@codemirror/view';
import type { SyntaxNode } from '@lezer/common';
import type { SyntaxNode, Tree } from '@lezer/common';
import type { ChannelDictionary, CommandDictionary, ParameterDictionary } from '@nasa-jpl/aerie-ampcs';
import ChevronDownIcon from '@nasa-jpl/stellar/icons/chevron_down.svg?component';
import CollapseIcon from 'bootstrap-icons/icons/arrow-bar-down.svg?component';
Expand Down Expand Up @@ -102,6 +102,7 @@
let menu: Menu;
let outputFormats: IOutputFormat[];
let selectedNode: SyntaxNode | null;
let currentTree: Tree;
let commandInfoMapper: CommandInfoMapper = new SeqNCommandInfoMapper();
let selectedOutputFormat: IOutputFormat | undefined;
let toggleSeqJsonPreview: boolean = false;
Expand Down Expand Up @@ -226,7 +227,7 @@
}
}

$: showOutputs = !isInVmlMode && !!outputFormats.length;
$: showOutputs = !isInVmlMode && outputFormats.length > 0;
$: {
if (showOutputs) {
editorHeights = toggleSeqJsonPreview ? '1fr 3px 1fr' : '1.88fr 3px 80px';
Expand Down Expand Up @@ -361,6 +362,7 @@
commandInfoMapper = new SeqNCommandInfoMapper();
}
selectedNode = updatedSelectionNode;
currentTree = tree;
}
}

Expand Down Expand Up @@ -539,6 +541,7 @@
{#if !!commandDictionary && !!selectedNode}
<SelectedCommand
node={selectedNode}
tree={currentTree}
{channelDictionary}
{commandDictionary}
{commandInfoMapper}
Expand Down
101 changes: 77 additions & 24 deletions src/components/sequencing/form/ArgEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
import {
getMissingArgDefs,
isFswCommandArgumentBoolean,
isFswCommandArgumentEnum,
isFswCommandArgumentRepeat,
isFswCommandArgumentVarString,
isNumberArg,
quoteEscape,
unquoteUnescape,
type ArgTextDef,
} from './../../../utilities/codemirror/codemirror-utils';
import AddMissingArgsButton from './AddMissingArgsButton.svelte';
Expand All @@ -26,24 +28,46 @@
export let setInEditor: (token: SyntaxNode, val: string) => void;
export let addDefaultArgs: (commandNode: SyntaxNode, argDefs: FswCommandArgument[]) => void;
export let commandInfoMapper: CommandInfoMapper;
export let variablesInScope: string[];

let argDef: FswCommandArgument | undefined = undefined;
let enableRepeatAdd: boolean = false;
let isSymbol: boolean = false;

$: argDef = argInfo.argDef;

$: {
isSymbol = commandInfoMapper.isArgumentNodeOfVariableType(argInfo.node ?? null);
if (!!argDef && isSymbol) {
argDef = {
arg_type: 'enum',
bit_length: null,
default_value: null,
description: argDef.description,
enum_name: 'variables',
name: argDef.name,
range: variablesInScope,
};
}
}

$: enableRepeatAdd =
argInfo.argDef &&
isFswCommandArgumentRepeat(argInfo.argDef) &&
argInfo.children &&
argInfo.argDef.repeat &&
argInfo.children.length < argInfo.argDef.repeat.arguments.length * (argInfo.argDef.repeat.max ?? Infinity);
argDef !== undefined &&
isFswCommandArgumentRepeat(argDef) &&
argInfo.children !== undefined &&
argDef.repeat !== null &&
argInfo.children.length < argDef.repeat.arguments.length * (argDef.repeat.max ?? Infinity);

function addRepeatTuple() {
const repeatArgs = argInfo.argDef && isFswCommandArgumentRepeat(argInfo.argDef) && argInfo.argDef.repeat?.arguments;
const repeatArgs = argDef && isFswCommandArgumentRepeat(argDef) && argDef.repeat?.arguments;
if (argInfo.node && repeatArgs) {
addDefaultArgs(argInfo.node, repeatArgs);
}
}
</script>

<fieldset>
{#if !argInfo.argDef}
{#if !argDef}
{#if argInfo.text}
<div class="st-typography-medium" title="Unknown Argument">Unknown Argument</div>
<ExtraArgumentEditor
Expand All @@ -56,16 +80,38 @@
/>
{/if}
{:else}
<ArgTitle argDef={argInfo.argDef} />
{#if argInfo.argDef.arg_type === 'enum' && argInfo.node}
{#if argInfo.argDef}
<ArgTitle
argDef={argInfo.argDef}
{commandInfoMapper}
argumentValueCategory={isSymbol ? 'Symbol' : 'Literal'}
setInEditor={val => {
if (argInfo.node) {
setInEditor(argInfo.node, val);
}
}}
/>
{/if}
{#if isSymbol && isFswCommandArgumentEnum(argDef)}
<div class="st-typography-small-caps">Reference</div>
<EnumEditor
{argDef}
initVal={argInfo.text ?? ''}
setInEditor={val => {
if (argInfo.node) {
setInEditor(argInfo.node, val);
}
}}
/>
{:else if isFswCommandArgumentEnum(argDef) && argInfo.node}
{#if commandInfoMapper.nodeTypeEnumCompatible(argInfo.node)}
<EnumEditor
{commandDictionary}
argDef={argInfo.argDef}
initVal={argInfo.text ?? ''}
{argDef}
initVal={unquoteUnescape(argInfo.text ?? '')}
setInEditor={val => {
if (argInfo.node) {
setInEditor(argInfo.node, val);
setInEditor(argInfo.node, quoteEscape(val));
}
}}
/>
Expand All @@ -81,40 +127,47 @@
Convert to enum type
</button>
{/if}
{:else if isNumberArg(argInfo.argDef) && commandInfoMapper.nodeTypeNumberCompatible(argInfo.node ?? null)}
{:else if isNumberArg(argDef) && commandInfoMapper.nodeTypeNumberCompatible(argInfo.node ?? null)}
<NumEditor
argDef={argInfo.argDef}
initVal={Number(argInfo.text) ?? argInfo.argDef.default_value ?? 0}
{argDef}
initVal={Number(argInfo.text) ?? argDef.default_value ?? 0}
setInEditor={val => {
if (argInfo.node) {
setInEditor(argInfo.node, val.toString());
}
}}
/>
{:else if isFswCommandArgumentVarString(argInfo.argDef)}
{:else if isFswCommandArgumentVarString(argDef)}
<StringEditor
argDef={argInfo.argDef}
{argDef}
initVal={argInfo.text ?? ''}
setInEditor={val => {
if (argInfo.node) {
setInEditor(argInfo.node, val);
}
}}
/>
{:else if isFswCommandArgumentBoolean(argInfo.argDef)}
{:else if isFswCommandArgumentBoolean(argDef)}
<BooleanEditor
argDef={argInfo.argDef}
{argDef}
initVal={argInfo.text ?? ''}
setInEditor={val => {
if (argInfo.node) {
setInEditor(argInfo.node, val);
}
}}
/>
{:else if isFswCommandArgumentRepeat(argInfo.argDef) && !!argInfo.children}
{:else if isFswCommandArgumentRepeat(argDef) && !!argInfo.children}
{#each argInfo.children as childArgInfo}
{#if childArgInfo.node}
<svelte:self argInfo={childArgInfo} {commandInfoMapper} {commandDictionary} {setInEditor} {addDefaultArgs} />
<svelte:self
argInfo={childArgInfo}
{commandInfoMapper}
{commandDictionary}
{setInEditor}
{addDefaultArgs}
{variablesInScope}
/>
{/if}
{/each}
{#if argInfo.children.find(childArgInfo => !childArgInfo.node)}
Expand All @@ -125,15 +178,15 @@
}
}}
/>
{:else if !!argInfo.argDef.repeat}
{:else if !!argDef.repeat}
<div>
<button
class="st-button secondary"
disabled={!enableRepeatAdd}
on:click={addRepeatTuple}
title={`Add additional set of argument values to ${argInfo.argDef.name} repeat array`}
title={`Add additional set of argument values to ${argDef.name} repeat array`}
>
Add {argInfo.argDef.name} tuple
Add {argDef.name} tuple
</button>
</div>
{/if}
Expand Down
88 changes: 71 additions & 17 deletions src/components/sequencing/form/ArgTitle.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
<script lang="ts">
import type { FswCommandArgument } from '@nasa-jpl/aerie-ampcs';
import { isArray } from 'lodash-es';
import type { CommandInfoMapper } from '../../../utilities/codemirror/commandInfoMapper';
import { getTarget } from '../../../utilities/generic';
import Collapse from '../../Collapse.svelte';
import {
isFswCommandArgumentFloat,
Expand All @@ -13,10 +15,19 @@
} from './../../../utilities/codemirror/codemirror-utils';

export let argDef: FswCommandArgument;
export let commandInfoMapper: CommandInfoMapper;
export let setInEditor: (val: string) => void;
export let argumentValueCategory: 'Literal' | 'Symbol';

$: title = getArgTitle(argDef);
let title: string = '';
let typeInfo: string = '';
let formattedRange: string = '';

function compactType(argDef: FswCommandArgument) {
$: typeInfo = compactType(argDef);
$: title = getArgTitle(argDef, typeInfo);
$: formattedRange = formatRange(argDef);

function compactType(argDef: FswCommandArgument): string {
if (isFswCommandArgumentUnsigned(argDef)) {
return `U${argDef.bit_length}`;
} else if (isFswCommandArgumentInteger(argDef)) {
Expand All @@ -30,7 +41,14 @@
return '';
}

function getArgTitle(argDef: FswCommandArgument) {
function formatRange(argDef: FswCommandArgument): string {
if ('range' in argDef && argDef.range !== null && !isArray(argDef.range)) {
return `[${argDef.range.min} – ${argDef.range.max}]`;
}
return '';
}

function getArgTitle(argDef: FswCommandArgument, typeInfo: string): string {
if (
isFswCommandArgumentRepeat(argDef) &&
typeof argDef.repeat?.max === 'number' &&
Expand All @@ -39,29 +57,65 @@
return `${argDef.name} - [${argDef.repeat?.min}, ${argDef.repeat?.max}] sets`;
}

let compactTypeInfo = compactType(argDef);
if (compactTypeInfo) {
compactTypeInfo = ` [${compactTypeInfo}]`;
}
let base = `${argDef.name}${compactTypeInfo}`;
if ('range' in argDef && argDef.range) {
if (isArray(argDef.range)) {
base += ` [${argDef.range.join(', ')}]`;
} else {
base += ` [${argDef.range.min} – ${argDef.range.max}]`;
}
}
const bracketedTypeInfo = typeInfo && ` [${typeInfo}]`;
const base = `${argDef.name}${bracketedTypeInfo} ${formatRange(argDef)}`;

if ('units' in argDef) {
return `${base} – (${argDef.units})`;
}

return base;
}

function onValueTypeChange(event: Event) {
const { value } = getTarget(event);
if (value === 'Literal') {
setInEditor(commandInfoMapper.getDefaultValueForArgumentDef(argDef, {}));
} else {
setInEditor('VARIABLE_OR_CONSTANT_NAME');
}
}
</script>

<Collapse headerHeight={24} padContent={false} {title} defaultExpanded={false}>
<div style="padding-bottom: 4px">
{argDef.description}
<div class="w-100 labeled-values" style="padding-bottom: 4px">
{#if formattedRange}
<div>Range</div>
<div>{formattedRange}</div>
{/if}

{#if typeInfo}
<div>Type</div>
<div>{typeInfo}</div>
{/if}

{#if argDef.description}
<div>Description</div>
<div>
{argDef.description}
</div>
{/if}

<div>Value Type</div>

<select class="st-select" required bind:value={argumentValueCategory} on:change={onValueTypeChange}>
<option value="Literal"> Literal </option>
<option value="Symbol"> Symbol </option>
</select>
</div>
</Collapse>

<style>
.labeled-values {
align-content: center;
align-items: top;
column-gap: 3px;
display: grid;
grid-template-columns: max-content 1fr;
row-gap: 2px;
}

.labeled-values > div:nth-child(odd) {
font-weight: bold;
}
</style>
12 changes: 5 additions & 7 deletions src/components/sequencing/form/EnumEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,25 @@
<script lang="ts">
import type { CommandDictionary, FswCommandArgumentEnum } from '@nasa-jpl/aerie-ampcs';
import type { SelectedDropdownOptionValue } from '../../../types/dropdown';
import { quoteEscape, unquoteUnescape } from '../../../utilities/codemirror/codemirror-utils';
import { quoteEscape } from '../../../utilities/codemirror/codemirror-utils';
import SearchableDropdown from '../../ui/SearchableDropdown.svelte';

const SEARCH_THRESHOLD = 100;
const MAX_SEARCH_ITEMS = 1_000;

export let argDef: FswCommandArgumentEnum;
export let commandDictionary: CommandDictionary;
export let commandDictionary: CommandDictionary | null = null;
export let initVal: string;
export let setInEditor: (val: string) => void;

let enumValues: string[];
let isValueInEnum: boolean = false;
let value: string;

$: value = unquoteUnescape(initVal);
$: enumValues = commandDictionary.enumMap[argDef.enum_name]?.values?.map(v => v.symbol) ?? argDef.range ?? [];
$: value = initVal;
$: enumValues = commandDictionary?.enumMap[argDef.enum_name]?.values?.map(v => v.symbol) ?? argDef.range ?? [];
$: isValueInEnum = !!enumValues.find(ev => ev === value);
$: {
setInEditor(quoteEscape(value));
}
$: setInEditor(value);
$: options = enumValues.map(ev => ({
display: ev,
value: ev,
Expand Down
Loading
Loading