Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
10 changes: 10 additions & 0 deletions extension/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,16 @@ RL1 {
}
```

#### Highlighting
Strings in `.stpa` files support inline markers that highlight specific parts of the text. These markers allow formatting such as italic, bold, underline, and strikethrough.
By default, markers are hidden during normal reading and only appear when the cursor is placed within the highlighted section. One can switch to editor mode using the toggle in the top-right corner of the interface. In this mode, all markers are always visible, regardless of cursor position.
If a marker character needs to be displayed literally (without it being interpreted as formatting), one can escape it with a backslash (`\`).
Supported markers are:
- `*<text>*` for italic
- `**<text>**` for bold
- `_<text>_` for underline
- `~<text>~` for strikethrough

### FTA

The file in which the analysis is done must have `.fta` as its file ending. Each component type in the fault tree has its own section. Components are stated with an ID and a descriptions. Afterwards, Conditions for Inhibit gates can be stated. The actual fault tree is than stated with a top event and the gates leading to this event. The gates can be annotated with a description as well. The gate types Or, And, Inhibit, and n/k are supported.
Expand Down
22 changes: 22 additions & 0 deletions extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,18 @@
"icon": "$(table)",
"category": "Context Table"
},
{
"command": "pasta.editor.editorMode",
"title": "Switch to read mode",
"icon": "$(edit)",
"category": "navigation"
},
{
"command": "pasta.editor.watchMode",
"title": "Switch to editor mode",
"icon": "$(book)",
"category": "navigation"
},
{
"command": "pasta.diagram.fit",
"title": "Fit to Screen",
Expand Down Expand Up @@ -443,6 +455,16 @@
"command": "pasta.contextTable.open",
"when": "editorLangId == 'stpa'",
"group": "navigation"
},
{
"command": "pasta.editor.editorMode",
"when": "pasta.highlightingActive && editorLangId == 'stpa' && pasta.markersVisible",
"group": "navigation"
},
{
"command": "pasta.editor.watchMode",
"when": "pasta.highlightingActive && editorLangId == 'stpa' && !pasta.markersVisible",
"group": "navigation"
}
],
"explorer/context": [
Expand Down
2 changes: 1 addition & 1 deletion extension/src-language-server/diagram-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ import { StpaServices } from "./stpa/stpa-module.js";
import { SynthesisOptions } from "./synthesis-options.js";

// matches id of a node to its expansion state. True means expanded, false and undefined means collapsed
export let expansionState = new Map<string, boolean>();
export const expansionState = new Map<string, boolean>();

export class PastaDiagramServer extends SnippetDiagramServer {
protected synthesisOptions: SynthesisOptions | undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
} from "./stpa-model.js";
import { StpaSynthesisOptions } from "./stpa-synthesis-options.js";
import { getCommonAncestor, sortPorts } from "./utils.js";
import { getRawStringInnerFromCst, stripInlineMarkers } from "../../utils.js";

/**
* Creates the control structure diagram for the given {@code controlStructure}.
Expand Down Expand Up @@ -263,7 +264,9 @@ export function translateCommandsToEdges(
const label: string[] = [];
for (let i = 0; i < edge.comms.length; i++) {
const com = edge.comms[i];
label.push(com.label);
const raw = getRawStringInnerFromCst(com) ?? com.label ?? "";
const stripped = stripInlineMarkers(raw);
Comment thread
Tokessa marked this conversation as resolved.
Outdated
label.push(stripped);
}
createEdgeForCommand(source, target, edgeId, edgeType, label, idToSNode, idCache, edges, controlActions);
}
Expand Down Expand Up @@ -371,8 +374,10 @@ export function translateIOToEdgeAndNode(
// create the label of the edge
const label: string[] = [];
for (let i = 0; i < io.length; i++) {
const command = io[i];
label.push(command.label);
const com = io[i];
const raw = getRawStringInnerFromCst(com) ?? com.label ?? "";
const stripped = stripInlineMarkers(raw);
label.push(stripped);
}

let graphComponents: (CSNode | CSEdge)[] = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
getTargets,
setLevelsForSTPANodes,
} from "./utils.js";
import { getRawStringInnerFromCst } from "../../utils.js";

/**
* Creates the relationship graph for the STPA model.
Expand Down Expand Up @@ -375,7 +376,7 @@ export function generateSTPANode(
node.name,
options,
idCache,
isContext(node) ? createUCAContextDescription(node) : node.description
isContext(node) ? createUCAContextDescription(node) : getRawStringInnerFromCst(node) ?? node.description ?? ""
);
// if the hierarchy option is true, the subcomponents are added as children to the parent
if (options.getHierarchy() && isHazard(node) && node.subComponents.length !== 0) {
Expand Down
85 changes: 85 additions & 0 deletions extension/src-language-server/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,90 @@ import { SLabel } from "sprotty-protocol";
import { URI } from "vscode-uri";
import { StpaValidator } from "./stpa/services/stpa-validator.js";
import { labelManagementValue } from "./synthesis-options.js";
import { MarkerDefinition, INLINE_MARKER_DEFINITIONS } from "../src/utils-classes.js";
Comment thread
Tokessa marked this conversation as resolved.

function escapeForRegexLiteral(s: string): string {
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

/**
* Removes all markdown-style markers that would be hidden by the InlineMarkdownDecorator.
* @param input The input string with markdown markers
* @param markerConfigs Optional marker configurations. If not provided, uses the default configs.
* @returns The string with hidden markers and escape backslashes removed
*/
export function stripInlineMarkers(
input: string,
markerConfigs: readonly MarkerDefinition[] = INLINE_MARKER_DEFINITIONS
): string {
let result = input;

// Remove marker pairs (Only matches markers that aren't escaped)
for (const config of markerConfigs) {
const escapedMarker = escapeForRegexLiteral(config.marker);
const regex = new RegExp(`(?<!\\\\)(${escapedMarker}+)(.+?)(?<!\\\\)\\1(?!${escapedMarker})`, "g");

result = result.replace(regex, (match, markers, content) => {
return content;
});
}

// Remove all escape backslashes before markers and backslashes
const markers = new Set<string>(markerConfigs.map(c => c.marker));
markers.add("\\");

const alternation = Array.from(markers).map(escapeForRegexLiteral).join("|");
if (alternation) {
const escapeRegex = new RegExp(`\\\\(${alternation})`, "g");

result = result.replace(escapeRegex, "$1");
}
return result;
}

/**
* Return the raw inner content of the first STRING token found for the given AST node.
* @param node The AST node to extract the string from.
* @returns the inner text (without surrounding quotes) or undefined when not available.
*/
export function getRawStringInnerFromCst(node: any): string | undefined {
if (!node || !node.$cstNode) {
return undefined;
}

const cst = node.$cstNode;

// depth-first search for a leaf token that contains the quoted text
try {
const stack: any[] = [cst];
while (stack.length > 0) {
const n = stack.pop();
if (!n) {
continue;
}

// If composite node with children, push them
if (Array.isArray((n as any).children) && (n as any).children.length > 0) {
for (let i = (n as any).children.length - 1; i >= 0; --i) {
stack.push((n as any).children[i]);
}
continue;
}

const img = (n as any).text ?? undefined;
if (typeof img === "string" && img.length >= 2) {
const first = img[0];
const last = img[img.length - 1];
if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
// return without surrounding quotes
return img.substring(1, img.length - 1);
}
}
}
} catch (e) {
console.error("Error while extracting raw string inner content:", e);
}
}

/**
* Determines the model for {@code uri}.
Expand Down Expand Up @@ -55,6 +139,7 @@ export function getDescription(
idCache: IdCache<AstNode>
): SLabel[] {
const labels: SLabel[] = [];
description = stripInlineMarkers(description);
const words = description.split(" ");
let current = "";
switch (labelManagement) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export function FeatherIcon(props: { iconId: string }): VNode {
// be necessary anyways.
const classes: Record<string, boolean> = {"feather": true};
classes[`feather-${props.iconId}`] = true;
const icon = icons[props.iconId as keyof typeof icons];

return <svg
style={{
Expand All @@ -44,6 +45,6 @@ export function FeatherIcon(props: { iconId: string }): VNode {
strokeLinejoin: 'round'
}}
class={classes}
props={{innerHTML: icons[props.iconId].toString()}}
props={{innerHTML: icon ? icon.toString() : ""}}
/>;
}
Loading
Loading