@parallelworks/ui 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/editor.worker-CuBRIwi-.js +14776 -0
- package/dist/assets/json.worker-8ytS02vz.js +22161 -0
- package/dist/assets/yaml.worker-DJC-VUcv.js +48367 -0
- package/dist/chunks/Indicator-fF8AVbjQ.js +158 -0
- package/dist/chunks/Loader-DQiZ75fa.js +25 -0
- package/dist/chunks/MarkdownImpl-Bq4QhrtA.js +33 -0
- package/dist/chunks/Monaco-CP_cL9gp.js +745 -0
- package/dist/chunks/Provider-C2VtPnh5.js +151 -0
- package/dist/chunks/SwitchToggleSmall-FcsaYSTs.js +14 -0
- package/dist/chunks/Table-DWuRINhu.js +855 -0
- package/dist/chunks/Tooltip-WgbiC_nd.js +71 -0
- package/dist/chunks/lib-DTgL8klR.js +191 -0
- package/dist/chunks/workers-Cjfjb8E7.js +119 -0
- package/dist/components/CollapsiblePanel.d.ts +27 -0
- package/dist/components/CopyToClipboard.d.ts +17 -0
- package/dist/components/Dropdown.d.ts +24 -0
- package/dist/components/Indicator.d.ts +4 -0
- package/dist/components/Listbox.d.ts +18 -0
- package/dist/components/Markdown.d.ts +7 -0
- package/dist/components/MarkdownImpl.d.ts +2 -0
- package/dist/components/MultiSelect.d.ts +19 -0
- package/dist/components/Overview.d.ts +12 -0
- package/dist/components/Provider.d.ts +145 -0
- package/dist/components/SectionHeader.d.ts +6 -0
- package/dist/components/SwitchToggle.d.ts +12 -0
- package/dist/components/SwitchToggleSmall.d.ts +6 -0
- package/dist/components/dropdownUtils.d.ts +18 -0
- package/dist/editor/Editor.d.ts +2 -0
- package/dist/editor/EditorField.d.ts +11 -0
- package/dist/editor/FormCodePanel.d.ts +8 -0
- package/dist/editor/Monaco.d.ts +31 -0
- package/dist/editor/index.d.ts +6 -0
- package/dist/editor/index.js +219 -0
- package/dist/editor/themes.d.ts +4 -0
- package/dist/editor/workers.d.ts +6 -0
- package/dist/form/FieldWrapper.d.ts +10 -0
- package/dist/form/Fields/BooleanField.d.ts +12 -0
- package/dist/form/Fields/BucketReadOnlyField.d.ts +7 -0
- package/dist/form/Fields/CheckboxGroupField.d.ts +18 -0
- package/dist/form/Fields/ColorField.d.ts +8 -0
- package/dist/form/Fields/DropdownField.d.ts +24 -0
- package/dist/form/Fields/DurationField.d.ts +13 -0
- package/dist/form/Fields/LabelField.d.ts +9 -0
- package/dist/form/Fields/MultiDropdownField.d.ts +18 -0
- package/dist/form/Fields/NumberField.d.ts +13 -0
- package/dist/form/Fields/ObjectField.d.ts +8 -0
- package/dist/form/Fields/RadioField.d.ts +9 -0
- package/dist/form/Fields/RangeField.d.ts +11 -0
- package/dist/form/Fields/ResourceOptionIcon.d.ts +11 -0
- package/dist/form/Fields/StepField.d.ts +3 -0
- package/dist/form/Fields/StringField.d.ts +17 -0
- package/dist/form/Fields/TextAreaField.d.ts +8 -0
- package/dist/form/Fields/durationValidity.d.ts +2 -0
- package/dist/form/Form.d.ts +105 -0
- package/dist/form/SpecialTypes/DropdownWrapper.d.ts +14 -0
- package/dist/form/SpecialTypes/FormikCustomDropdown.d.ts +53 -0
- package/dist/form/SpecialTypes/FormikCustomInput.d.ts +17 -0
- package/dist/form/SpecialTypes/FormikMultiSelectDropdown.d.ts +13 -0
- package/dist/form/SpecialTypes/FormikNumericInput.d.ts +9 -0
- package/dist/form/SpecialTypes/index.d.ts +3 -0
- package/dist/form/Wizard/WizardContainer.d.ts +5 -0
- package/dist/form/Wizard/WizardNavigation.d.ts +2 -0
- package/dist/form/Wizard/WizardStepContent.d.ts +8 -0
- package/dist/form/Wizard/WizardStepIndicator.d.ts +2 -0
- package/dist/form/Wizard/types.d.ts +171 -0
- package/dist/form/Wizard/useWizardState.d.ts +28 -0
- package/dist/form/Wizard/utils.d.ts +2 -0
- package/dist/form/fieldRegistry.d.ts +7 -0
- package/dist/form/index.d.ts +16 -0
- package/dist/form/index.js +2317 -0
- package/dist/form/lib.d.ts +15 -0
- package/dist/form/types/fieldComponentTypes.d.ts +32 -0
- package/dist/form/types/fieldTypes.d.ts +80 -0
- package/dist/form/types/workflow-schema.d.ts +1107 -0
- package/dist/form/useParsedOpts.d.ts +5 -0
- package/dist/form/usePrevious.d.ts +1 -0
- package/dist/form/utils/checkShowIfObject.d.ts +1 -0
- package/dist/form/utils/getParentValue.d.ts +1 -0
- package/dist/form/utils/getPlaceholder.d.ts +2 -0
- package/dist/form/utils/getValueUsingPath.d.ts +1 -0
- package/dist/form/utils/pwResolver.d.ts +18 -0
- package/dist/form/utils/secondaryField.d.ts +8 -0
- package/dist/form/utils/slurmDuration.d.ts +15 -0
- package/dist/icons.d.ts +1 -1
- package/dist/icons.js +2 -2
- package/dist/index.d.ts +17 -0
- package/dist/index.js +107 -323
- package/dist/logviewer/LogViewer.d.ts +18 -0
- package/dist/logviewer/index.d.ts +1 -0
- package/dist/logviewer/index.js +534 -0
- package/dist/logviewer/logviewer.css +70 -0
- package/dist/styles.css +1 -1
- package/dist/workflow/AnnotationBanner.d.ts +5 -0
- package/dist/workflow/Collapse.d.ts +6 -0
- package/dist/workflow/DependencyGraph.d.ts +15 -0
- package/dist/workflow/JobSummary.d.ts +38 -0
- package/dist/workflow/MatrixGroup.d.ts +41 -0
- package/dist/workflow/Reveal.d.ts +7 -0
- package/dist/workflow/TreeView.d.ts +42 -0
- package/dist/workflow/index.d.ts +1 -0
- package/dist/workflow/index.js +2330 -0
- package/package.json +60 -6
|
@@ -0,0 +1,745 @@
|
|
|
1
|
+
import { s as useWorkflowJsonResolver } from "./Provider-C2VtPnh5.js";
|
|
2
|
+
import { n as defineEditorThemes, r as getThemeName, t as setupMonacoWorkers } from "./workers-Cjfjb8E7.js";
|
|
3
|
+
import cx from "classnames";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
import { useEffect, useRef, useState } from "react";
|
|
6
|
+
import { dumpYaml } from "@parallelworks/workflow-parser";
|
|
7
|
+
import yaml from "js-yaml";
|
|
8
|
+
import * as monaco from "monaco-editor";
|
|
9
|
+
import { configureMonacoYaml } from "monaco-yaml";
|
|
10
|
+
//#region src/editor/Monaco.tsx
|
|
11
|
+
setupMonacoWorkers();
|
|
12
|
+
var activeWorkflowJsonResolver;
|
|
13
|
+
function readIsDark() {
|
|
14
|
+
const scheme = getComputedStyle(document.documentElement).colorScheme;
|
|
15
|
+
const dark = scheme.includes("dark");
|
|
16
|
+
if (dark !== scheme.includes("light")) return dark;
|
|
17
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
18
|
+
}
|
|
19
|
+
function useCssIsDark() {
|
|
20
|
+
const [isDark, setIsDark] = useState(readIsDark);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
const update = () => setIsDark(readIsDark());
|
|
23
|
+
const media = window.matchMedia("(prefers-color-scheme: dark)");
|
|
24
|
+
media.addEventListener("change", update);
|
|
25
|
+
const observer = new MutationObserver(update);
|
|
26
|
+
observer.observe(document.documentElement, {
|
|
27
|
+
attributes: true,
|
|
28
|
+
attributeFilter: [
|
|
29
|
+
"style",
|
|
30
|
+
"class",
|
|
31
|
+
"data-theme"
|
|
32
|
+
]
|
|
33
|
+
});
|
|
34
|
+
return () => {
|
|
35
|
+
media.removeEventListener("change", update);
|
|
36
|
+
observer.disconnect();
|
|
37
|
+
};
|
|
38
|
+
}, []);
|
|
39
|
+
return isDark;
|
|
40
|
+
}
|
|
41
|
+
var monacoWithWorkerBridge = {
|
|
42
|
+
...monaco,
|
|
43
|
+
editor: {
|
|
44
|
+
...monaco.editor,
|
|
45
|
+
createWebWorker: (opts) => "worker" in opts ? monaco.editor.createWebWorker(opts) : monaco.createWebWorker(opts)
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
var monacoYamlConfigured = false;
|
|
49
|
+
function ensureMonacoYamlConfigured() {
|
|
50
|
+
if (monacoYamlConfigured) return;
|
|
51
|
+
monacoYamlConfigured = true;
|
|
52
|
+
configureMonacoYaml(monacoWithWorkerBridge, {
|
|
53
|
+
enableSchemaRequest: true,
|
|
54
|
+
completion: true,
|
|
55
|
+
validate: true,
|
|
56
|
+
hover: true,
|
|
57
|
+
format: { enable: true },
|
|
58
|
+
schemas: [{
|
|
59
|
+
fileMatch: ["file:///workflow.yaml"],
|
|
60
|
+
uri: `${window.location.origin}/workflow.schema.json`
|
|
61
|
+
}]
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
var workflowCompletionContexts = /* @__PURE__ */ new Map();
|
|
65
|
+
var registeredCompletionProviders = [];
|
|
66
|
+
var workflowCompletionProvidersRegistered = false;
|
|
67
|
+
var noop = () => {};
|
|
68
|
+
function MonacoEditor({ className, disabled, onChange = noop, onValidate = noop, width = "100%", height = "100%", path = "file:///default", marketplaceItems = [], workflows = [], ...options }) {
|
|
69
|
+
const isDark = useCssIsDark();
|
|
70
|
+
const resolveWorkflowJson = useWorkflowJsonResolver();
|
|
71
|
+
const monacoRef = useRef(null);
|
|
72
|
+
const isSyncingValueRef = useRef(false);
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
activeWorkflowJsonResolver = resolveWorkflowJson;
|
|
75
|
+
}, [resolveWorkflowJson]);
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (!monacoRef.current) return;
|
|
78
|
+
defineEditorThemes(monaco.editor);
|
|
79
|
+
monaco.editor.setTheme(getThemeName(isDark, disabled));
|
|
80
|
+
monacoRef.current.updateOptions({ readOnly: disabled });
|
|
81
|
+
}, [disabled, isDark]);
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (!monacoRef.current) return;
|
|
84
|
+
const model = monacoRef.current.getModel();
|
|
85
|
+
if (model && options.value !== void 0 && model.getValue() !== options.value) {
|
|
86
|
+
isSyncingValueRef.current = true;
|
|
87
|
+
model.setValue(options.value);
|
|
88
|
+
isSyncingValueRef.current = false;
|
|
89
|
+
}
|
|
90
|
+
}, [options.value]);
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
return () => {
|
|
93
|
+
monacoRef.current?.dispose();
|
|
94
|
+
monacoRef.current = null;
|
|
95
|
+
};
|
|
96
|
+
}, []);
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
const uri = monaco.Uri.parse(path).toString();
|
|
99
|
+
if (marketplaceItems.length > 0 || workflows.length > 0) workflowCompletionContexts.set(uri, {
|
|
100
|
+
marketplaceItems,
|
|
101
|
+
workflows
|
|
102
|
+
});
|
|
103
|
+
else workflowCompletionContexts.delete(uri);
|
|
104
|
+
return () => {
|
|
105
|
+
workflowCompletionContexts.delete(uri);
|
|
106
|
+
};
|
|
107
|
+
}, [
|
|
108
|
+
marketplaceItems,
|
|
109
|
+
workflows,
|
|
110
|
+
path
|
|
111
|
+
]);
|
|
112
|
+
/** Strips CR (\r) — Windows line endings break bash scripts. */
|
|
113
|
+
const onChangeWrapper = (value) => {
|
|
114
|
+
value = value.replace(/\r/g, "");
|
|
115
|
+
onChange(value);
|
|
116
|
+
};
|
|
117
|
+
return /* @__PURE__ */ jsx("div", {
|
|
118
|
+
style: {
|
|
119
|
+
width,
|
|
120
|
+
height
|
|
121
|
+
},
|
|
122
|
+
className: cx("relative border rounded", className),
|
|
123
|
+
ref: (ref) => {
|
|
124
|
+
if (ref && !monacoRef.current) {
|
|
125
|
+
monaco.editor.getModel(monaco.Uri.parse(path))?.dispose();
|
|
126
|
+
defineEditorThemes(monaco.editor);
|
|
127
|
+
monaco.editor.setTheme(getThemeName(isDark, disabled));
|
|
128
|
+
const model = monaco.editor.createModel(options.value ?? "", options.language, monaco.Uri.parse(path));
|
|
129
|
+
const editor = monaco.editor.create(ref, {
|
|
130
|
+
automaticLayout: true,
|
|
131
|
+
model,
|
|
132
|
+
...options,
|
|
133
|
+
readOnly: disabled || options.readOnly,
|
|
134
|
+
fixedOverflowWidgets: true,
|
|
135
|
+
suggest: {
|
|
136
|
+
preview: true,
|
|
137
|
+
showWords: false,
|
|
138
|
+
showInlineDetails: true
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
model.onDidChangeContent(() => {
|
|
142
|
+
if (isSyncingValueRef.current) return;
|
|
143
|
+
onChangeWrapper(editor.getValue());
|
|
144
|
+
});
|
|
145
|
+
model.onDidChangeDecorations(() => {
|
|
146
|
+
onValidate(monaco.editor.getModelMarkers({ resource: model.uri }));
|
|
147
|
+
});
|
|
148
|
+
ensureMonacoYamlConfigured();
|
|
149
|
+
monacoRef.current = editor;
|
|
150
|
+
if (workflowCompletionProvidersRegistered) return;
|
|
151
|
+
workflowCompletionProvidersRegistered = true;
|
|
152
|
+
registeredCompletionProviders.push(monaco.languages.registerCompletionItemProvider("yaml", {
|
|
153
|
+
triggerCharacters: [
|
|
154
|
+
"/",
|
|
155
|
+
" ",
|
|
156
|
+
"@"
|
|
157
|
+
],
|
|
158
|
+
provideCompletionItems: async (model, position) => {
|
|
159
|
+
const context = workflowCompletionContexts.get(model.uri.toString());
|
|
160
|
+
if (!context) return { suggestions: [] };
|
|
161
|
+
const { marketplaceItems, workflows } = context;
|
|
162
|
+
const lineContent = model.getLineContent(position.lineNumber);
|
|
163
|
+
const textUntilPos = lineContent.substring(0, position.column - 1);
|
|
164
|
+
const defaultItem = {
|
|
165
|
+
kind: monaco.languages.CompletionItemKind.Value,
|
|
166
|
+
range: {
|
|
167
|
+
startLineNumber: position.lineNumber,
|
|
168
|
+
startColumn: position.column,
|
|
169
|
+
endLineNumber: position.lineNumber,
|
|
170
|
+
endColumn: position.column
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
let suggestions = [];
|
|
174
|
+
if (!/^\s*(?:-\s+)?uses\s*:\s*([^\n\r]*)$/.test(textUntilPos)) return { suggestions };
|
|
175
|
+
const marketIndex = textUntilPos.lastIndexOf("marketplace/");
|
|
176
|
+
const workflowIndex = textUntilPos.lastIndexOf("workflow/");
|
|
177
|
+
const pwIndex = textUntilPos.lastIndexOf("parallelworks/");
|
|
178
|
+
const githubIndex = textUntilPos.lastIndexOf("github/");
|
|
179
|
+
const usesText = textUntilPos.match(/^\s*(?:-\s+)?uses\s*:\s*([^\n\r]*)$/)?.[1]?.trim() ?? "";
|
|
180
|
+
if (marketIndex === -1 && workflowIndex === -1 && pwIndex === -1 && githubIndex === -1) {
|
|
181
|
+
if (usesText.trim().length > 0) return { suggestions };
|
|
182
|
+
return { suggestions: [
|
|
183
|
+
{
|
|
184
|
+
label: "marketplace",
|
|
185
|
+
sortText: "2",
|
|
186
|
+
insertText: "marketplace",
|
|
187
|
+
detail: "use a workflow from the marketplace",
|
|
188
|
+
...defaultItem
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
label: "workflow",
|
|
192
|
+
sortText: "3",
|
|
193
|
+
insertText: "workflow",
|
|
194
|
+
detail: "use one of your workflows",
|
|
195
|
+
...defaultItem
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
label: "github",
|
|
199
|
+
sortText: "4",
|
|
200
|
+
insertText: "github",
|
|
201
|
+
detail: "use a workflow from a public GitHub repository",
|
|
202
|
+
...defaultItem
|
|
203
|
+
}
|
|
204
|
+
] };
|
|
205
|
+
}
|
|
206
|
+
const noExtra = [
|
|
207
|
+
"marketplace/",
|
|
208
|
+
"workflow/",
|
|
209
|
+
"parallelworks/",
|
|
210
|
+
"github/"
|
|
211
|
+
].includes(usesText);
|
|
212
|
+
if (workflowIndex !== -1 && noExtra) {
|
|
213
|
+
if (lineContent.substring(workflowIndex, position.column - 1).split("/").length === 2) suggestions = workflows.map((item) => {
|
|
214
|
+
const name = item.name ?? "";
|
|
215
|
+
return {
|
|
216
|
+
label: item.displayName && item.displayName !== name ? `${item.displayName} (${name})` : name,
|
|
217
|
+
insertText: name,
|
|
218
|
+
detail: item.description ? item.description : `${item.type ?? ""} workflow`,
|
|
219
|
+
...defaultItem
|
|
220
|
+
};
|
|
221
|
+
});
|
|
222
|
+
} else if (marketIndex !== -1) {
|
|
223
|
+
const sections = lineContent.substring(marketIndex, position.column - 1).split("/");
|
|
224
|
+
if (sections.length === 2 && noExtra) suggestions = marketplaceItems.filter((item) => item.type === "workflow").map((item) => {
|
|
225
|
+
const slug = item.slug ?? "";
|
|
226
|
+
return {
|
|
227
|
+
label: item.name && item.name !== slug ? `${item.name} (${slug})` : slug,
|
|
228
|
+
insertText: slug,
|
|
229
|
+
detail: item.description ? item.description : `${item.subtype ?? ""} workflow`,
|
|
230
|
+
...defaultItem
|
|
231
|
+
};
|
|
232
|
+
});
|
|
233
|
+
else if (sections.length === 3 && sections[2] === "") {
|
|
234
|
+
const mpItem = marketplaceItems.find((item) => item.slug === sections[1]);
|
|
235
|
+
if (mpItem && mpItem.versions) suggestions = [...mpItem.versions.map((v) => {
|
|
236
|
+
return {
|
|
237
|
+
label: v,
|
|
238
|
+
insertText: v,
|
|
239
|
+
...defaultItem
|
|
240
|
+
};
|
|
241
|
+
}), {
|
|
242
|
+
label: "latest",
|
|
243
|
+
insertText: "latest",
|
|
244
|
+
...defaultItem
|
|
245
|
+
}];
|
|
246
|
+
}
|
|
247
|
+
} else if (githubIndex !== -1) {
|
|
248
|
+
const afterGithub = usesText.replace("github/", "");
|
|
249
|
+
if (afterGithub === "") suggestions = [{
|
|
250
|
+
label: "Type a GitHub username or organization",
|
|
251
|
+
insertText: "",
|
|
252
|
+
detail: "e.g. github/myorg/",
|
|
253
|
+
...defaultItem
|
|
254
|
+
}];
|
|
255
|
+
else if (!afterGithub.includes("/")) {} else {
|
|
256
|
+
const ghOwner = afterGithub.split("/")[0];
|
|
257
|
+
const rest = afterGithub.split("/").slice(1).join("/");
|
|
258
|
+
if (rest === "") try {
|
|
259
|
+
const resp = await fetch(`https://api.github.com/users/${ghOwner}/repos?per_page=100&sort=updated`);
|
|
260
|
+
if (resp.ok) suggestions = (await resp.json()).map((r) => ({
|
|
261
|
+
label: r.description ? `${r.name} — ${r.description}` : r.name,
|
|
262
|
+
insertText: r.name,
|
|
263
|
+
detail: r.description || "GitHub repository",
|
|
264
|
+
...defaultItem
|
|
265
|
+
}));
|
|
266
|
+
} catch {}
|
|
267
|
+
else {
|
|
268
|
+
let ghRepo = "";
|
|
269
|
+
let isRefTrigger = false;
|
|
270
|
+
let replaceSlash = false;
|
|
271
|
+
const slashRefMatch = rest.match(/^([^@/]+)\/$/);
|
|
272
|
+
if (slashRefMatch) {
|
|
273
|
+
ghRepo = slashRefMatch[1];
|
|
274
|
+
isRefTrigger = true;
|
|
275
|
+
replaceSlash = true;
|
|
276
|
+
} else if (rest.includes("@")) {
|
|
277
|
+
const atRepo = rest.split("@")[0];
|
|
278
|
+
const refPart = rest.split("@").slice(1).join("@");
|
|
279
|
+
if (atRepo && refPart === "") {
|
|
280
|
+
ghRepo = atRepo;
|
|
281
|
+
isRefTrigger = true;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if (isRefTrigger && ghRepo) {
|
|
285
|
+
const slashToAtEdit = replaceSlash ? [{
|
|
286
|
+
range: {
|
|
287
|
+
startLineNumber: position.lineNumber,
|
|
288
|
+
startColumn: position.column - 1,
|
|
289
|
+
endLineNumber: position.lineNumber,
|
|
290
|
+
endColumn: position.column
|
|
291
|
+
},
|
|
292
|
+
text: "@"
|
|
293
|
+
}] : [];
|
|
294
|
+
try {
|
|
295
|
+
const [branchResp, tagResp, commitResp] = await Promise.all([
|
|
296
|
+
fetch(`https://api.github.com/repos/${ghOwner}/${ghRepo}/branches?per_page=100`),
|
|
297
|
+
fetch(`https://api.github.com/repos/${ghOwner}/${ghRepo}/tags?per_page=100`),
|
|
298
|
+
fetch(`https://api.github.com/repos/${ghOwner}/${ghRepo}/commits?per_page=10`)
|
|
299
|
+
]);
|
|
300
|
+
if (branchResp.ok) {
|
|
301
|
+
const branches = await branchResp.json();
|
|
302
|
+
suggestions.push(...branches.map((b) => ({
|
|
303
|
+
label: b.name,
|
|
304
|
+
insertText: b.name,
|
|
305
|
+
detail: "branch",
|
|
306
|
+
additionalTextEdits: slashToAtEdit,
|
|
307
|
+
...defaultItem
|
|
308
|
+
})));
|
|
309
|
+
}
|
|
310
|
+
if (tagResp.ok) {
|
|
311
|
+
const tags = await tagResp.json();
|
|
312
|
+
suggestions.push(...tags.map((t) => ({
|
|
313
|
+
label: t.name,
|
|
314
|
+
insertText: t.name,
|
|
315
|
+
detail: "tag",
|
|
316
|
+
additionalTextEdits: slashToAtEdit,
|
|
317
|
+
...defaultItem
|
|
318
|
+
})));
|
|
319
|
+
}
|
|
320
|
+
if (commitResp.ok) {
|
|
321
|
+
const commits = await commitResp.json();
|
|
322
|
+
suggestions.push(...commits.map((c) => ({
|
|
323
|
+
label: `${c.sha.slice(0, 7)} — ${c.commit.message.split("\n")[0]}`,
|
|
324
|
+
insertText: c.sha,
|
|
325
|
+
detail: "commit",
|
|
326
|
+
additionalTextEdits: slashToAtEdit,
|
|
327
|
+
...defaultItem
|
|
328
|
+
})));
|
|
329
|
+
}
|
|
330
|
+
} catch {}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return { suggestions };
|
|
336
|
+
}
|
|
337
|
+
}));
|
|
338
|
+
registeredCompletionProviders.push(monaco.languages.registerCompletionItemProvider("yaml", {
|
|
339
|
+
triggerCharacters: [" ", "\n"],
|
|
340
|
+
provideCompletionItems: async (model, position) => {
|
|
341
|
+
if (!workflowCompletionContexts.has(model.uri.toString())) return { suggestions: [] };
|
|
342
|
+
const lineNumber = position.lineNumber;
|
|
343
|
+
const lineContent = model.getLineContent(lineNumber);
|
|
344
|
+
const textUntilPos = lineContent.substring(0, position.column - 1);
|
|
345
|
+
if (/^\s*\$yaml\s*:\s*$/.test(textUntilPos)) {
|
|
346
|
+
for (let ln = lineNumber - 1; ln >= Math.max(lineNumber - 30, 1); ln--) {
|
|
347
|
+
const m = model.getLineContent(ln).trim().match(/^(?:-\s+)?uses\s*:\s*github\/([^/]+)\/([^@]+)@(.+)$/);
|
|
348
|
+
if (m) {
|
|
349
|
+
const [, owner, repo, ref] = m;
|
|
350
|
+
try {
|
|
351
|
+
const resp = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/trees/${ref}?recursive=1`);
|
|
352
|
+
if (resp.ok) {
|
|
353
|
+
const data = await resp.json();
|
|
354
|
+
const defaultItem = {
|
|
355
|
+
kind: monaco.languages.CompletionItemKind.File,
|
|
356
|
+
range: {
|
|
357
|
+
startLineNumber: lineNumber,
|
|
358
|
+
startColumn: position.column,
|
|
359
|
+
endLineNumber: lineNumber,
|
|
360
|
+
endColumn: position.column
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
return { suggestions: (data.tree || []).filter((item) => item.type === "blob" && (item.path.endsWith(".yaml") || item.path.endsWith(".yml"))).map((f) => ({
|
|
364
|
+
label: f.path,
|
|
365
|
+
insertText: f.path,
|
|
366
|
+
detail: "yaml file",
|
|
367
|
+
...defaultItem
|
|
368
|
+
})) };
|
|
369
|
+
}
|
|
370
|
+
} catch {}
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return { suggestions: [] };
|
|
375
|
+
}
|
|
376
|
+
if (/^\s*\$thumbnail\s*:\s*$/.test(textUntilPos)) {
|
|
377
|
+
for (let ln = lineNumber - 1; ln >= Math.max(lineNumber - 30, 1); ln--) {
|
|
378
|
+
const m = model.getLineContent(ln).trim().match(/^(?:-\s+)?uses\s*:\s*github\/([^/]+)\/([^@]+)@(.+)$/);
|
|
379
|
+
if (m) {
|
|
380
|
+
const [, owner, repo, ref] = m;
|
|
381
|
+
try {
|
|
382
|
+
const resp = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/trees/${ref}?recursive=1`);
|
|
383
|
+
if (resp.ok) {
|
|
384
|
+
const data = await resp.json();
|
|
385
|
+
const defaultItem = {
|
|
386
|
+
kind: monaco.languages.CompletionItemKind.File,
|
|
387
|
+
range: {
|
|
388
|
+
startLineNumber: lineNumber,
|
|
389
|
+
startColumn: position.column,
|
|
390
|
+
endLineNumber: lineNumber,
|
|
391
|
+
endColumn: position.column
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
const imageExts = [
|
|
395
|
+
".png",
|
|
396
|
+
".jpg",
|
|
397
|
+
".jpeg",
|
|
398
|
+
".gif",
|
|
399
|
+
".svg",
|
|
400
|
+
".webp"
|
|
401
|
+
];
|
|
402
|
+
return { suggestions: (data.tree || []).filter((item) => item.type === "blob" && imageExts.some((ext) => item.path.toLowerCase().endsWith(ext))).map((f) => ({
|
|
403
|
+
label: f.path,
|
|
404
|
+
insertText: f.path,
|
|
405
|
+
detail: "image file",
|
|
406
|
+
...defaultItem
|
|
407
|
+
})) };
|
|
408
|
+
}
|
|
409
|
+
} catch {}
|
|
410
|
+
break;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return { suggestions: [] };
|
|
414
|
+
}
|
|
415
|
+
const isOnWithLine = /^\s*with\s*:\s*([^\n\r]*)$/.test(textUntilPos);
|
|
416
|
+
const isOnYamlLine = /^\s*\$(yaml|thumbnail)\s*:\s*\S/.test(textUntilPos);
|
|
417
|
+
let withLineNum = -1;
|
|
418
|
+
if (isOnWithLine) withLineNum = lineNumber;
|
|
419
|
+
else if (isOnYamlLine) for (let ln = lineNumber - 1; ln >= Math.max(lineNumber - 20, 1); ln--) {
|
|
420
|
+
const content = model.getLineContent(ln);
|
|
421
|
+
if (content.trim() === "") continue;
|
|
422
|
+
if (/^\s*with\s*:\s*([^\n\r]*)$/.test(content)) {
|
|
423
|
+
withLineNum = ln;
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
else if (textUntilPos.trim() === "") for (let ln = lineNumber - 1; ln >= Math.max(lineNumber - 20, 1); ln--) {
|
|
428
|
+
const content = model.getLineContent(ln);
|
|
429
|
+
if (content.trim() === "") continue;
|
|
430
|
+
if (/^\s*with\s*:\s*([^\n\r]*)$/.test(content)) {
|
|
431
|
+
withLineNum = ln;
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (withLineNum === -1) return { suggestions: [] };
|
|
436
|
+
if (!isOnWithLine && !isOnYamlLine && withLineNum !== -1) for (let ln = withLineNum + 1; ln < lineNumber; ln++) {
|
|
437
|
+
const content = model.getLineContent(ln).trim();
|
|
438
|
+
if (content === "") continue;
|
|
439
|
+
if (/^\$(yaml|thumbnail)\s*:/.test(content)) continue;
|
|
440
|
+
return { suggestions: [] };
|
|
441
|
+
}
|
|
442
|
+
const needsNewline = isOnWithLine || isOnYamlLine;
|
|
443
|
+
const needsIndent = isOnWithLine;
|
|
444
|
+
let usesLine = "";
|
|
445
|
+
let usesLineNum = -1;
|
|
446
|
+
for (let ln = lineNumber - 1; ln >= Math.max(lineNumber - 20, 1); ln--) {
|
|
447
|
+
const content = model.getLineContent(ln).trim();
|
|
448
|
+
if (/^(?:-\s+)?uses\s*:\s*(.+)$/.test(content)) {
|
|
449
|
+
usesLine = content;
|
|
450
|
+
usesLineNum = ln;
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
const needsWithLine = usesLineNum !== -1 && withLineNum < usesLineNum;
|
|
455
|
+
let withIndent = "";
|
|
456
|
+
if (needsWithLine && usesLineNum !== -1) {
|
|
457
|
+
const indentMatch = model.getLineContent(usesLineNum).match(/^(\s*(?:-\s+)?)uses/);
|
|
458
|
+
if (indentMatch) withIndent = " ".repeat(indentMatch[1].length);
|
|
459
|
+
}
|
|
460
|
+
const defaultItem = {
|
|
461
|
+
kind: monaco.languages.CompletionItemKind.Value,
|
|
462
|
+
range: needsWithLine ? {
|
|
463
|
+
startLineNumber: lineNumber,
|
|
464
|
+
startColumn: 1,
|
|
465
|
+
endLineNumber: lineNumber,
|
|
466
|
+
endColumn: lineContent.length + 1
|
|
467
|
+
} : {
|
|
468
|
+
startLineNumber: lineNumber,
|
|
469
|
+
startColumn: position.column,
|
|
470
|
+
endLineNumber: lineNumber,
|
|
471
|
+
endColumn: position.column
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
if (usesLine === "") return { suggestions: [] };
|
|
475
|
+
const m = usesLine.match(/^(?:-\s+)?uses\s*:\s*(.+)$/);
|
|
476
|
+
if (!m) return { suggestions: [] };
|
|
477
|
+
const usesValue = m[1].trim();
|
|
478
|
+
if (!usesValue) return { suggestions: [] };
|
|
479
|
+
if (usesValue.startsWith("parallelworks/")) {
|
|
480
|
+
if (!needsWithLine && !isOnWithLine) return { suggestions: [] };
|
|
481
|
+
const actionDef = {
|
|
482
|
+
"parallelworks/checkout": {
|
|
483
|
+
label: "checkout",
|
|
484
|
+
required: {
|
|
485
|
+
repo: "https://github.com/parallelworks/interactive_session.git",
|
|
486
|
+
branch: "main"
|
|
487
|
+
}
|
|
488
|
+
},
|
|
489
|
+
"parallelworks/scheduler-agent": {
|
|
490
|
+
label: "scheduler-agent",
|
|
491
|
+
required: { "scheduler-type": "slurm" }
|
|
492
|
+
},
|
|
493
|
+
"parallelworks/wait-for-agent": {
|
|
494
|
+
label: "wait-for-agent",
|
|
495
|
+
required: { agentId: "${{ needs.JOB.steps.STEP.outputs.agentId }}" }
|
|
496
|
+
},
|
|
497
|
+
"parallelworks/cancel-jobs": {
|
|
498
|
+
label: "cancel-jobs",
|
|
499
|
+
required: {}
|
|
500
|
+
},
|
|
501
|
+
"parallelworks/update-session": {
|
|
502
|
+
label: "update-session",
|
|
503
|
+
required: { name: "session-name" }
|
|
504
|
+
}
|
|
505
|
+
}[usesValue];
|
|
506
|
+
if (!actionDef || Object.keys(actionDef.required).length === 0) return { suggestions: [] };
|
|
507
|
+
const withObj = { ...actionDef.required };
|
|
508
|
+
const withSuggestionYaml = prepareWithSuggestion(needsNewline, needsIndent, needsWithLine, withIndent, withObj);
|
|
509
|
+
return { suggestions: [{
|
|
510
|
+
label: actionDef.label + " inputs",
|
|
511
|
+
detail: "inputs for the " + actionDef.label + " action",
|
|
512
|
+
sortText: "1",
|
|
513
|
+
insertText: withSuggestionYaml,
|
|
514
|
+
...defaultItem
|
|
515
|
+
}] };
|
|
516
|
+
}
|
|
517
|
+
if (!(usesValue.startsWith("marketplace/") || usesValue.startsWith("workflow/") || usesValue.startsWith("github/"))) return { suggestions: [] };
|
|
518
|
+
let inputs = {};
|
|
519
|
+
let usesWorkflowRef;
|
|
520
|
+
const isGithub = usesValue.startsWith("github/");
|
|
521
|
+
const hasYamlOverride = isGithub && (needsWithLine ? hasYamlNearUses(model, usesLineNum) : scanForYamlInWithBlock(model, withLineNum) !== void 0);
|
|
522
|
+
if (isGithub) {
|
|
523
|
+
const [ownerRepo, ref] = usesValue.replace("github/", "").split("@");
|
|
524
|
+
if (!ownerRepo || !ref) return { suggestions: [] };
|
|
525
|
+
const [owner, repo] = ownerRepo.split("/");
|
|
526
|
+
if (!owner || !repo) return { suggestions: [] };
|
|
527
|
+
usesWorkflowRef = [ownerRepo];
|
|
528
|
+
const yamlPath = (!needsWithLine ? scanForYamlInWithBlock(model, withLineNum) : void 0) || "workflow.yaml";
|
|
529
|
+
try {
|
|
530
|
+
const response = await fetch(`https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${yamlPath}`);
|
|
531
|
+
if (response.ok) {
|
|
532
|
+
const text = await response.text();
|
|
533
|
+
inputs = yaml.load(text)?.on?.execute?.inputs || {};
|
|
534
|
+
}
|
|
535
|
+
} catch {}
|
|
536
|
+
} else {
|
|
537
|
+
usesWorkflowRef = usesValue.split("/").slice(1);
|
|
538
|
+
const resolveJson = activeWorkflowJsonResolver;
|
|
539
|
+
if (!resolveJson) return { suggestions: [] };
|
|
540
|
+
try {
|
|
541
|
+
const data = await resolveJson(usesValue.startsWith("marketplace/") ? {
|
|
542
|
+
kind: "marketplace",
|
|
543
|
+
slug: usesWorkflowRef[0],
|
|
544
|
+
version: usesWorkflowRef.length > 1 ? usesWorkflowRef[1] : "latest"
|
|
545
|
+
} : {
|
|
546
|
+
kind: "workflow",
|
|
547
|
+
name: usesWorkflowRef[0]
|
|
548
|
+
});
|
|
549
|
+
if (!data) return { suggestions: [] };
|
|
550
|
+
inputs = data?.on?.execute?.inputs || {};
|
|
551
|
+
} catch {
|
|
552
|
+
return { suggestions: [] };
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
if (Object.keys(inputs).length === 0) {
|
|
556
|
+
const noInputSuggestions = [{
|
|
557
|
+
label: "No inputs found",
|
|
558
|
+
detail: usesWorkflowRef[0] + " has no inputs.",
|
|
559
|
+
insertText: "",
|
|
560
|
+
...defaultItem
|
|
561
|
+
}];
|
|
562
|
+
if (isGithub) {
|
|
563
|
+
if (!hasYamlOverride) noInputSuggestions.unshift({
|
|
564
|
+
label: "$yaml",
|
|
565
|
+
detail: "Specify the yaml path in the GitHub repo (default: workflow.yaml)",
|
|
566
|
+
sortText: "0",
|
|
567
|
+
insertText: needsWithLine ? `${withIndent}with:\n${withIndent} $yaml:` : needsIndent ? "\n $yaml:" : "$yaml:",
|
|
568
|
+
...defaultItem
|
|
569
|
+
});
|
|
570
|
+
noInputSuggestions.unshift({
|
|
571
|
+
label: "$thumbnail",
|
|
572
|
+
detail: "Specify a thumbnail image path or URL for this subworkflow",
|
|
573
|
+
sortText: "0a",
|
|
574
|
+
insertText: needsWithLine ? `${withIndent}with:\n${withIndent} $thumbnail:` : needsIndent ? "\n $thumbnail:" : "$thumbnail:",
|
|
575
|
+
...defaultItem
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
return { suggestions: noInputSuggestions };
|
|
579
|
+
}
|
|
580
|
+
let groupname = usesValue.replaceAll("/", "_").replaceAll(".", "-").replaceAll("@", "_");
|
|
581
|
+
const fullText = model.getValue();
|
|
582
|
+
let groupidx = 0;
|
|
583
|
+
try {
|
|
584
|
+
const basename = groupname;
|
|
585
|
+
while (true) if (yaml.load(fullText)?.on?.execute?.inputs?.[groupname]) {
|
|
586
|
+
groupidx++;
|
|
587
|
+
groupname = `${basename}_${groupidx}`;
|
|
588
|
+
} else break;
|
|
589
|
+
} catch {}
|
|
590
|
+
const withSuggestionYaml = prepareWithSuggestion(needsNewline, needsIndent, needsWithLine, withIndent, constructWithSuggestion(inputs, groupname));
|
|
591
|
+
const lines = fullText.split(/\r?\n/);
|
|
592
|
+
let inputsLineIndex = -1;
|
|
593
|
+
for (let i = 0; i < lines.length; i++) if (/^[ \t]*inputs\s*:\s*(#.*)?$/.test(lines[i])) {
|
|
594
|
+
const inputsIndentMatch = lines[i].match(/^([ \t]*)/);
|
|
595
|
+
const inputsIndent = inputsIndentMatch ? inputsIndentMatch[1].length : 0;
|
|
596
|
+
let executeIndex = -1;
|
|
597
|
+
for (let j = i - 1; j >= 0; j--) {
|
|
598
|
+
const m = lines[j].match(/^([ \t]*)([^\s:]+)\s*:\s*(#.*)?$/);
|
|
599
|
+
if (!m) continue;
|
|
600
|
+
const indent = m[1].length;
|
|
601
|
+
if (m[2] === "execute" && indent < inputsIndent) {
|
|
602
|
+
executeIndex = j;
|
|
603
|
+
break;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
if (executeIndex === -1) continue;
|
|
607
|
+
const execIndentMatch = lines[executeIndex].match(/^([ \t]*)/);
|
|
608
|
+
const execIndent = execIndentMatch ? execIndentMatch[1].length : 0;
|
|
609
|
+
let onFound = false;
|
|
610
|
+
for (let k = executeIndex - 1; k >= 0; k--) {
|
|
611
|
+
const mOn = lines[k].match(/^([ \t]*)(?:'on'|on)\s*:\s*(#.*)?$/);
|
|
612
|
+
if (!mOn) continue;
|
|
613
|
+
if (mOn[1].length < execIndent) {
|
|
614
|
+
onFound = true;
|
|
615
|
+
break;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
if (onFound) {
|
|
619
|
+
inputsLineIndex = i;
|
|
620
|
+
break;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
const additionalTextEdits = [];
|
|
624
|
+
const yamlInputs = ` ${groupname}:\nlabel: ${usesWorkflowRef[0]} inputs${groupidx === 0 ? "" : ` ${groupidx}`}\ntype: group\nitems:\n `.replaceAll("\n", "\n ") + rewriteInputExpressionsForGroup(dumpYaml(inputs), groupname).trim().replaceAll("\n", "\n ") + "\n";
|
|
625
|
+
if (inputsLineIndex !== -1) {
|
|
626
|
+
const insertLineNumber = inputsLineIndex + 2;
|
|
627
|
+
additionalTextEdits.push({
|
|
628
|
+
range: {
|
|
629
|
+
startLineNumber: insertLineNumber,
|
|
630
|
+
startColumn: 1,
|
|
631
|
+
endLineNumber: insertLineNumber,
|
|
632
|
+
endColumn: 1
|
|
633
|
+
},
|
|
634
|
+
text: yamlInputs
|
|
635
|
+
});
|
|
636
|
+
} else {
|
|
637
|
+
if (position.lineNumber === lines.length) {
|
|
638
|
+
const editor = monaco.editor.getEditors().find((e) => e.getModel()?.uri.toString() === model.uri.toString());
|
|
639
|
+
editor?.executeEdits("ensure-trailing-newline", [{
|
|
640
|
+
range: new monaco.Range(lines.length, lineContent.length + 1, lines.length, lineContent.length + 1),
|
|
641
|
+
text: "\n",
|
|
642
|
+
forceMoveMarkers: true
|
|
643
|
+
}], () => {
|
|
644
|
+
const sel = editor?.getSelection();
|
|
645
|
+
return sel ? [sel] : null;
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
additionalTextEdits.push({
|
|
649
|
+
range: {
|
|
650
|
+
startLineNumber: lines.length + 1,
|
|
651
|
+
startColumn: 1,
|
|
652
|
+
endLineNumber: lines.length + 1,
|
|
653
|
+
endColumn: 1
|
|
654
|
+
},
|
|
655
|
+
text: "\non:\n execute:\n inputs:\n" + yamlInputs
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
const suggestions = [{
|
|
659
|
+
label: usesWorkflowRef[0] + " inputs",
|
|
660
|
+
detail: "all input expressions and dynamic form additions",
|
|
661
|
+
sortText: "1",
|
|
662
|
+
insertText: withSuggestionYaml,
|
|
663
|
+
additionalTextEdits,
|
|
664
|
+
...defaultItem
|
|
665
|
+
}, {
|
|
666
|
+
label: usesWorkflowRef[0] + " inputs only",
|
|
667
|
+
detail: "all input expressions only, no dynamic form additions",
|
|
668
|
+
sortText: "2",
|
|
669
|
+
insertText: withSuggestionYaml,
|
|
670
|
+
...defaultItem
|
|
671
|
+
}];
|
|
672
|
+
if (isGithub) {
|
|
673
|
+
if (!hasYamlOverride) suggestions.unshift({
|
|
674
|
+
label: "$yaml (custom yaml path)",
|
|
675
|
+
detail: "Override the default workflow.yaml path in the GitHub repo",
|
|
676
|
+
sortText: "0",
|
|
677
|
+
insertText: needsWithLine ? `${withIndent}with:\n${withIndent} $yaml:` : needsIndent ? "\n $yaml:" : "$yaml:",
|
|
678
|
+
...defaultItem
|
|
679
|
+
});
|
|
680
|
+
suggestions.unshift({
|
|
681
|
+
label: "$thumbnail (custom thumbnail)",
|
|
682
|
+
detail: "Specify a thumbnail image path or URL for this subworkflow",
|
|
683
|
+
sortText: "0a",
|
|
684
|
+
insertText: needsWithLine ? `${withIndent}with:\n${withIndent} $thumbnail:` : needsIndent ? "\n $thumbnail:" : "$thumbnail:",
|
|
685
|
+
...defaultItem
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
return { suggestions };
|
|
689
|
+
}
|
|
690
|
+
}));
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
/** When a workflow is imported as a group, rewrites bare `inputs.X` → `inputs.<groupname>.X` so expressions still resolve. */
|
|
696
|
+
function rewriteInputExpressionsForGroup(yamlStr, groupname) {
|
|
697
|
+
return yamlStr.replace(/\$\{\{(.*?)\}\}/g, (_match, expr) => {
|
|
698
|
+
return "${{ " + expr.replace(/\binputs\./g, `inputs.${groupname}.`).trim() + " }}";
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
function constructWithSuggestion(inputs, path) {
|
|
702
|
+
const withSuggestion = {};
|
|
703
|
+
Object.keys(inputs).map((k) => {
|
|
704
|
+
const input = inputs[k];
|
|
705
|
+
if (input.type === "group" && input.items) withSuggestion[k] = constructWithSuggestion(input.items, path + "." + k);
|
|
706
|
+
else withSuggestion[k] = "${{ inputs." + path + "." + k + " }}";
|
|
707
|
+
});
|
|
708
|
+
return withSuggestion;
|
|
709
|
+
}
|
|
710
|
+
function prepareWithSuggestion(needsNewline, needsIndent, needsWithLine, withIndent, withSuggestion) {
|
|
711
|
+
let withSuggestionYaml = dumpYaml(withSuggestion);
|
|
712
|
+
if (needsWithLine) {
|
|
713
|
+
const childIndent = withIndent + " ";
|
|
714
|
+
withSuggestionYaml = (withIndent + "with:\n" + withSuggestionYaml).replaceAll("\n", "\n" + childIndent);
|
|
715
|
+
} else if (needsNewline && needsIndent) withSuggestionYaml = ("\n" + withSuggestionYaml).replaceAll("\n", "\n ");
|
|
716
|
+
else if (needsNewline) withSuggestionYaml = "\n" + withSuggestionYaml;
|
|
717
|
+
return withSuggestionYaml;
|
|
718
|
+
}
|
|
719
|
+
/** Scan lines below a `uses:` line for a `$yaml:` key, stopping at another step. */
|
|
720
|
+
function hasYamlNearUses(model, usesLineNumber) {
|
|
721
|
+
const totalLines = model.getLineCount();
|
|
722
|
+
for (let ln = usesLineNumber + 1; ln <= Math.min(usesLineNumber + 20, totalLines); ln++) {
|
|
723
|
+
const content = model.getLineContent(ln).trim();
|
|
724
|
+
if (content === "") continue;
|
|
725
|
+
if (/^-\s/.test(content)) break;
|
|
726
|
+
if (/^\$yaml\s*:/.test(content)) return true;
|
|
727
|
+
}
|
|
728
|
+
return false;
|
|
729
|
+
}
|
|
730
|
+
/** Scan lines below a `with:` line for a `$yaml:` key within the same indent block. */
|
|
731
|
+
function scanForYamlInWithBlock(model, withLineNumber) {
|
|
732
|
+
const withIndentMatch = model.getLineContent(withLineNumber).match(/^(\s*)/);
|
|
733
|
+
const withIndent = withIndentMatch ? withIndentMatch[1].length : 0;
|
|
734
|
+
const totalLines = model.getLineCount();
|
|
735
|
+
for (let ln = withLineNumber + 1; ln <= totalLines; ln++) {
|
|
736
|
+
const line = model.getLineContent(ln);
|
|
737
|
+
if (line.trim() === "") continue;
|
|
738
|
+
const indentMatch = line.match(/^(\s*)/);
|
|
739
|
+
if ((indentMatch ? indentMatch[1].length : 0) <= withIndent) break;
|
|
740
|
+
const yamlMatch = line.match(/^\s*\$yaml\s*:\s*(.*)$/);
|
|
741
|
+
if (yamlMatch) return yamlMatch[1].trim();
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
//#endregion
|
|
745
|
+
export { MonacoEditor as default };
|