diff --git a/src/tests/dspf.test.ts b/src/tests/dspf.test.ts index cc5469a..655d3e0 100644 --- a/src/tests/dspf.test.ts +++ b/src/tests/dspf.test.ts @@ -1,5 +1,6 @@ import { expect, describe, it } from "vitest"; import { DdsLineRange, DisplayFile, FieldInfo } from "../ui/dspf"; +import exp from "constants"; describe('DisplayFile tests', () => { @@ -28,15 +29,15 @@ describe('DisplayFile tests', () => { let dds = new DisplayFile(); dds.parse(dspf1); - expect(dds.getRangeForFormat(`DONOTEXIST`)).toBeUndefined(); + expect(dds.getHeaderRangeForFormat(`DONOTEXIST`)).toBeUndefined(); let range: DdsLineRange | undefined; - range = dds.getRangeForFormat(`FMT1`); + range = dds.getHeaderRangeForFormat(`FMT1`); expect(range?.start).toBe(3); expect(range?.end).toBe(9); - range = dds.getRangeForFormat(`HEAD`); + range = dds.getHeaderRangeForFormat(`HEAD`); expect(range?.start).toBe(1); expect(range?.end).toBe(3); }); @@ -56,13 +57,48 @@ describe('DisplayFile tests', () => { range = dds.getRangeForField(`FORM1`, `FLD0102`); expect(range?.start).toBe(17); expect(range?.end).toBe(17); - }); - it('getLinesForField', () => { - + it('generates the same as what is provided', () => { let dds = new DisplayFile(); + dds.parse(dspf1); + + const form1 = dds.formats.find(f => f.name === `FORM1`); + expect(form1).toBeDefined(); + + const FLD0101 = form1?.fields.find(f => f.name === `FLD0101`); + expect(FLD0101).toBeDefined(); + expect(FLD0101?.keywords.length).toBe(2); + + const DSPATR = FLD0101?.keywords.find(k => k.name === `DSPATR`); + expect(DSPATR).toBeDefined(); + expect(DSPATR?.value).toBe(`PR`); + expect(DSPATR?.conditions.length).toBe(1); + + const cond = DSPATR?.conditions[0]; + expect(cond).toBeDefined(); + expect(cond?.indicator).toBe(20); + expect(cond?.negate).toBeFalsy(); + const generatedKeywordLines = DisplayFile.getLinesForKeyword(DSPATR!); + expect(generatedKeywordLines.length).toBe(1); + expect(generatedKeywordLines[0]).toBe(dspf1[15].trimEnd()); + + const generateFieldLines = DisplayFile.getLinesForField(FLD0101!); + expect(generateFieldLines.length).toBe(3); + + expect(generateFieldLines[0]).toBe(dspf1[14].trimEnd()); + expect(generateFieldLines[1]).toBe(dspf1[15].trimEnd()); + expect(generateFieldLines[2]).toBe(dspf1[16].trimEnd()); + + const generatedRecordFormatLines = DisplayFile.getHeaderLinesForFormat(form1!.name, form1!.keywords); + expect(generatedRecordFormatLines.length).toBe(2); + expect(generatedRecordFormatLines[0]).toBe(dspf1[12].trimEnd()); + expect(generatedRecordFormatLines[1]).toBe(dspf1[13].trimEnd()); + + }); + + it('getLinesForField', () => { let field = new FieldInfo(0); field.displayType = `const`; field.value = `Some text`; @@ -92,7 +128,6 @@ describe('DisplayFile tests', () => { expect(lines[0]).toBe(` A 4 10'Some text'`); expect(lines[1]).toBe(` A COLOR(BLU)`); expect(lines[2]).toBe(` A DSPATR(PR)`); - }); it('No duplicate RecordInfo', () => { diff --git a/src/ui/dspf.ts b/src/ui/dspf.ts index 7440563..d272a6e 100644 --- a/src/ui/dspf.ts +++ b/src/ui/dspf.ts @@ -230,8 +230,7 @@ export class DisplayFile { static parseConditionals(conditionColumns: string): Conditional[] { if (conditionColumns.trim() === "") {return [];} - /** @type {Conditional[]} */ - let conditionals = []; + let conditionals: Conditional[] = []; //TODO: something with condition //const condition = conditionColumns.substring(0, 1); //A (and) or O (or) @@ -351,6 +350,37 @@ export class DisplayFile { return result; } + private static conditionalGroups(conditions: Conditional[]) { + return conditions.reduce((acc, curr, index) => { + if (index % 3 === 0) { + acc.push([curr]); + } else { + acc[acc.length - 1].push(curr); + } + return acc; + }, [] as Conditional[][]); + } + + public static getLinesForKeyword(keyword: Keyword): string[] { + const lines: string[] = []; + + // Convert array into groups of three + const condition = this.conditionalGroups(keyword.conditions); + + const firstConditions = condition[0] || []; + const conditionStrings = firstConditions.map(c => `${c.negate ? 'N' : ' '}${c.indicator}`).join('').padEnd(9); + + lines.push(` A ${conditionStrings} ${keyword.name}${keyword.value ? `(${keyword.value})` : ``}`); + + for (let g = 1; g < condition.length; g++) { + const group = condition[g]; + const conditionStrings = group.map(c => `${c.negate ? 'N' : ' '}${c.indicator}`).join(''); + lines.push(` A ${conditionStrings}`); + } + + return lines; + } + public static getLinesForField(field: FieldInfo): string[] { const newLines: string[] = []; @@ -366,25 +396,27 @@ export class DisplayFile { const y = String(field.position.y).padStart(3, ` `); const displayType = FIELD_TYPE[field.displayType!]; + // Convert array into groups of three + const condition = this.conditionalGroups(field.conditions); + const firstConditions = condition[0] || []; + const conditionStrings = firstConditions.map(c => `${c.negate ? 'N' : ' '}${c.indicator}`).join('').padEnd(9); + if (field.displayType === `const`) { const value = field.value; newLines.push( - ` A ${y}${x}'${value}'`, + ` A ${conditionStrings} ${y}${x}'${value}'`, ); } else if (displayType && field.name) { const definitionType = field.type; const length = String(field.length).padStart(5); - const decimals = String(field.decimals).padStart(2); + const decimals = (field.type !== `A` ? String(field.decimals) : ``).padStart(2); newLines.push( - ` A ${field.name.padEnd(10)} ${length}${definitionType}${decimals}${displayType}${y}${x}`, + ` A ${conditionStrings} ${field.name.padEnd(10)} ${length}${definitionType}${decimals}${displayType}${y}${x}`, ); } for (const keyword of field.keywords) { - // TODO: support conditions - newLines.push( - ` A ${keyword.name}${keyword.value ? `(${keyword.value})` : ``}`, - ); + newLines.push(...DisplayFile.getLinesForKeyword(keyword)); } return newLines; @@ -437,24 +469,22 @@ export class DisplayFile { } // TODO: test cases - static getLinesForFormat(recordFormat: RecordInfo): string[] { + static getHeaderLinesForFormat(recordFormat: string, keywords: Keyword[]): string[] { const lines: string[] = []; - if (recordFormat.name !== GLOBAL_RECORD_NAME) { - lines.push(` A R ${recordFormat.name}`); + if (recordFormat) { + lines.push(` A R ${recordFormat}`); } - for (const keyword of recordFormat.keywords) { + for (const keyword of keywords) { // TODO: support conditions - lines.push( - ` A ${keyword.name}${keyword.value ? `(${keyword.value})` : ``}`, - ); + lines.push(...DisplayFile.getLinesForKeyword(keyword)); } return lines; } - public getRangeForFormat(recordFormat: string): DdsLineRange|undefined { + public getHeaderRangeForFormat(recordFormat: string): DdsLineRange|undefined { let range: DdsLineRange|undefined = undefined; const currentFormatI = this.formats.findIndex(format => format.name === recordFormat); if (currentFormatI > 0) { @@ -474,9 +504,9 @@ export class DisplayFile { } // TODO: test cases - public updateFormat(originalFormatName: string, newRecordFormat: RecordInfo): DdsUpdate|undefined { - const newLines = DisplayFile.getLinesForFormat(newRecordFormat); - let range = this.getRangeForFormat(originalFormatName); + public updateFormatHeader(originalFormatName: string, keywords: Keyword[]): DdsUpdate|undefined { + const newLines = DisplayFile.getHeaderLinesForFormat(originalFormatName, keywords); + let range = this.getHeaderRangeForFormat(originalFormatName); if (range) { range = { diff --git a/src/ui/index.ts b/src/ui/index.ts index fd3a29e..26c5346 100644 --- a/src/ui/index.ts +++ b/src/ui/index.ts @@ -1,7 +1,6 @@ -import { readFile, readFileSync } from "fs"; -import { WebviewViewProvider, WebviewView, Uri, CancellationToken, WebviewViewResolveContext, Webview, DiagnosticSeverity, window, WebviewPanel, ViewColumn, ExtensionContext, workspace, TextDocument, TextEdit, Range, WorkspaceEdit, Position } from "vscode"; -import { basename } from "path"; -import { DisplayFile, FieldInfo } from "./dspf"; +import { readFileSync } from "fs"; +import { Uri, Webview, window, WebviewPanel, ViewColumn, ExtensionContext, workspace, TextDocument, Range, WorkspaceEdit, Position } from "vscode"; +import { DisplayFile, FieldInfo, Keyword, RecordInfo } from "./dspf"; export class RendererWebview { @@ -126,7 +125,32 @@ export class RendererWebview { ); await workspace.applyEdit(workspaceEdit); - this.load(false); + this.load(false); //Field is updated on the client + } + } + } + break; + + case `updateFormat`: + // This does not update any of the fields in the record format, only the format header + recordFormat = message.recordFormat; + const newKeywords: Keyword[] = message.newKeywords; + + if (typeof recordFormat === `string` && Array.isArray(newKeywords)) { + const formatUpdate = this.dds?.updateFormatHeader(recordFormat, newKeywords); + + if (formatUpdate) { + if (formatUpdate.range && this.document) { + const workspaceEdit = new WorkspaceEdit(); + workspaceEdit.replace( + this.document.uri, + new Range(formatUpdate.range.start, 0, formatUpdate.range.end, 1000), + formatUpdate.newLines.join('\n'), // TOOD: use the correct EOL? + {label: `Update DDS Format`, needsConfirmation: false} + ); + + await workspace.applyEdit(workspaceEdit); + this.load(true); } } } diff --git a/webui/main.js b/webui/main.js index 4259145..fdc70f9 100644 --- a/webui/main.js +++ b/webui/main.js @@ -44,6 +44,8 @@ const timeFormats = { '*JIS': 'hh:mm:ss', }; +const GLOBAL_RECORD_FORMAT = `_GLOBAL`; + const vscode = acquireVsCodeApi(); const pxwPerChar = 8.45; @@ -92,7 +94,7 @@ function loadDDS(newDoc, type, withRerender = true) { activeDocumentType = type; if (withRerender) { - const validFormats = activeDocument.formats.filter(format => format.name !== `GLOBAL`); + const validFormats = activeDocument.formats.filter(format => format.name !== GLOBAL_RECORD_FORMAT); setTabs(validFormats.map(format => format.name), lastSelectedFormat); @@ -777,7 +779,9 @@ function updateRecordFormatSidebar(recordInfo, globalInfo) { sections.push({ title: `Format Keywords`, - html: createKeywordPanel(`keywords-${recordInfo.name}`, recordInfo.keywords), + html: createKeywordPanel(`keywords-${recordInfo.name}`, recordInfo.keywords, (keywords) => { + sendFormatHeaderUpdate(recordInfo.name, keywords); + }), open: true }); @@ -985,6 +989,18 @@ function sendFieldUpdate(recordFormat, originalFieldName, newFieldInfo) { } } +/** + * @param {string} recordFormat + * @param {Keyword[]} newKeywords + */ +function sendFormatHeaderUpdate(recordFormat, newKeywords) { + vscode.postMessage({ + command: `updateFormat`, + recordFormat, + newKeywords + }); +} + /** * Used to create panels for editable key/value lists. * @param {string} id