Skip to content

Commit

Permalink
Feature: filter variable completion items by context
Browse files Browse the repository at this point in the history
  • Loading branch information
MarkusEllyton committed Oct 25, 2024
1 parent 508d7a0 commit dc9281b
Show file tree
Hide file tree
Showing 8 changed files with 255 additions and 118 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),

## [Unreleased]

### Changed

- Variable completion items in cosimulation configuration files are now context-aware, only showing the relevant variables, i.e. either inputs, outputs or parameters.

## [0.1.2] - 2024-10-03

### Fixed
Expand Down
4 changes: 2 additions & 2 deletions TODOS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
- [x] Weird inconsistent spacing/tabs
- [x] The cosim file is not linted when it is opened or when the extension starts. Meaning when the extension first loads it won't catch errors until the file has been edited. Also, if an FMU is ever deleted, it won't show up as an error in the configuration file.
- [x] Remove dangling period in Axios error message.
- [ ] Filter autocompletion items for connections to only show input/output/parameters depending on context.
- [x] Filter autocompletion items for connections to only show input/output/parameters depending on context.
- [ ] Setup Actions to build extension package
- [ ] Additional testing - increase coverage in unit tests
- [ ] Demo video showing basic functionality of extension.
- [x] Demo video showing basic functionality of extension.
- [ ] Documentation - MkDocs, for reference: <https://github.com/INTO-CPS-Association/DTaaS/blob/feature/distributed-demo/docs/PUBLISH.md>

## v0.2.0 development
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"license": "SEE LICENSE IN LICENSE.md",
"description": "Co-simulation in VS Code",
"repository": "https://github.com/INTO-CPS-Association/Co-Simulation-Studio",
"version": "0.1.2",
"version": "0.1.3",
"icon": "into_cps_logo.png",
"engines": {
"vscode": "^1.82.0"
Expand Down
26 changes: 15 additions & 11 deletions src/fmu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,14 @@ import { getLogger } from 'logging'

const logger = getLogger()

interface ModelInput {
name: string
}

interface ModelOutput {
export interface ModelVariable {
name: string
}

export interface FMUModel {
inputs: ModelInput[]
outputs: ModelOutput[]
inputs: ModelVariable[]
outputs: ModelVariable[]
parameters: ModelVariable[]
}

export interface FMUSource {
Expand Down Expand Up @@ -97,8 +94,9 @@ export async function extractFMUModelFromPath(
}

const modelDescriptionObject = zipFile.file('modelDescription.xml')
const modelDescriptionContents =
await modelDescriptionObject?.async('nodebuffer')
const modelDescriptionContents = await modelDescriptionObject?.async(
'nodebuffer'
)

if (modelDescriptionContents) {
return parseXMLModelDescription(modelDescriptionContents)
Expand All @@ -119,8 +117,9 @@ export function parseXMLModelDescription(source: string | Buffer): FMUModel {
throw new Error('Failed to parse XML model description.')
}

const inputs: ModelInput[] = []
const outputs: ModelOutput[] = []
const inputs: ModelVariable[] = []
const outputs: ModelVariable[] = []
const parameters: ModelVariable[] = []

// TODO: update this code to use Zod schemas instead of optional chaining and nullish coalescing
const modelVariables =
Expand All @@ -138,11 +137,16 @@ export function parseXMLModelDescription(source: string | Buffer): FMUModel {
outputs.push({
name: mVar['@_name'],
})
} else if (varCausality === 'parameter') {
parameters.push({
name: mVar['@_name'],
})
}
}

return {
inputs,
outputs,
parameters,
}
}
55 changes: 51 additions & 4 deletions src/language-features/completion-items.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import * as vscode from 'vscode'
import { getNodePath } from 'jsonc-parser'
import { getNodePath, Node } from 'jsonc-parser'
import {
CosimulationConfiguration,
getFMUIdentifierFromConnectionString,
getStringContentRange,
isNodeString,
} from './utils'
import { ModelVariable } from 'fmu'

export class SimulationConfigCompletionItemProvider
implements vscode.CompletionItemProvider
Expand Down Expand Up @@ -85,6 +86,8 @@ export class SimulationConfigCompletionItemProvider
): Promise<vscode.CompletionItem[]> {
const completionNode = cosimConfig.getNodeAtPosition(position)

console.log(completionNode)

if (
!completionNode ||
!isNodeString(completionNode) ||
Expand All @@ -101,15 +104,38 @@ export class SimulationConfigCompletionItemProvider
return []
}

const validVariables =
await cosimConfig.getAllVariablesFromIdentifier(fmuIdentifier)
const fmuModel = await cosimConfig.getFMUModel(fmuIdentifier)

console.log(fmuModel)

if (!fmuModel) {
return []
}

const completionContext = this.getCompletionContext(completionNode)

console.log('Completion context', completionContext)

const completionVariables: ModelVariable[] = []

if (completionContext === 'input') {
completionVariables.push(...fmuModel.inputs)
} else if (completionContext === 'output') {
completionVariables.push(...fmuModel.outputs)
} else if (completionContext === 'parameter') {
completionVariables.push(...fmuModel.parameters)
}

const completionStrings = completionVariables.map(
(variable) => variable.name
)

// Get range of the nearest word following a period
const range = cosimConfig
.getDocument()
.getWordRangeAtPosition(position, /(?<=\.)\w+/)

const suggestions = validVariables.map((variable) => {
const suggestions = completionStrings.map((variable) => {
const completionItem = new vscode.CompletionItem(
variable,
vscode.CompletionItemKind.Property
Expand All @@ -121,4 +147,25 @@ export class SimulationConfigCompletionItemProvider

return suggestions
}

getCompletionContext(
completionNode: Node
): 'input' | 'output' | 'parameter' | null {
const nodePath = getNodePath(completionNode)

console.log('Node path:', nodePath)

if (nodePath.length === 2 && nodePath[0] === 'parameters') {
return 'parameter'
} else if (nodePath.length === 2 && nodePath[0] === 'connections') {
return 'output'
} else if (
nodePath.length === 3 &&
nodePath[0] === 'connections' &&
typeof nodePath[2] === 'number'
) {
return 'input'
}
return null
}
}
103 changes: 28 additions & 75 deletions test/fmu.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,33 @@ const dummyModelDescription = `
</ScalarVariable>
<ScalarVariable name="v1" causality="output">
</ScalarVariable>
<ScalarVariable name="c1" causality="parameter">
</ScalarVariable>
</ModelVariables>
</fmiModelDescription>
`

const dummyModel: FMUModel = {
inputs: [
{
name: 'fk',
},
],
outputs: [
{
name: 'x1',
},
{
name: 'v1',
},
],
parameters: [
{
name: 'c1',
},
],
}

describe('FMU Parsing', () => {
afterEach(() => {
jest.clearAllMocks()
Expand All @@ -40,21 +63,7 @@ describe('FMU Parsing', () => {
it('parses XML model description correctly', async () => {
const result = parseXMLModelDescription(dummyModelDescription)

expect(result).toEqual({
inputs: [
{
name: 'fk',
},
],
outputs: [
{
name: 'x1',
},
{
name: 'v1',
},
],
} satisfies FMUModel)
expect(result).toEqual(dummyModel)
})

it('throws when parsing invalid XML model description', async () => {
Expand Down Expand Up @@ -105,21 +114,7 @@ describe('FMU Parsing', () => {

const result = await extractFMUModelFromPath(Uri.file('file/path'))

expect(result).toEqual({
inputs: [
{
name: 'fk',
},
],
outputs: [
{
name: 'x1',
},
{
name: 'v1',
},
],
} satisfies FMUModel)
expect(result).toEqual(dummyModel)
})
})
describe('getFMUModelFromPath', () => {
Expand Down Expand Up @@ -172,21 +167,7 @@ describe('FMU Parsing', () => {
'file/path'
)

expect(result).toEqual({
inputs: [
{
name: 'fk',
},
],
outputs: [
{
name: 'x1',
},
{
name: 'v1',
},
],
} satisfies FMUModel)
expect(result).toEqual(dummyModel)
expect(vscode.workspace.fs.stat).toHaveBeenCalledWith(
Uri.file('/data/file/path')
)
Expand Down Expand Up @@ -239,21 +220,7 @@ describe('FMU Parsing', () => {
'file/path'
)

expect(result).toEqual({
inputs: [
{
name: 'fk',
},
],
outputs: [
{
name: 'x1',
},
{
name: 'v1',
},
],
} satisfies FMUModel)
expect(result).toEqual(dummyModel)

const secondResult = await getFMUModelFromPath(
workspaceFolder,
Expand Down Expand Up @@ -289,21 +256,7 @@ describe('FMU Parsing', () => {
'file/path'
)

expect(result).toEqual({
inputs: [
{
name: 'fk',
},
],
outputs: [
{
name: 'x1',
},
{
name: 'v1',
},
],
} satisfies FMUModel)
expect(result).toEqual(dummyModel)
;(vscode.workspace.fs.stat as jest.Mock).mockResolvedValue({
ctime: 1,
})
Expand Down
Loading

0 comments on commit dc9281b

Please sign in to comment.