Initial code import
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
import {
|
||||
TemplateToken,
|
||||
MAPPING_TYPE,
|
||||
SEQUENCE_TYPE,
|
||||
NULL_TYPE,
|
||||
} from "@github/actions-workflow-parser/templates/tokens/index";
|
||||
import { MappingToken } from "@github/actions-workflow-parser/templates/tokens/mapping-token";
|
||||
import { SequenceToken } from "@github/actions-workflow-parser/templates/tokens/sequence-token";
|
||||
import { Position } from "vscode-languageserver-textdocument";
|
||||
|
||||
export function findInnerToken(pos: Position, root?: TemplateToken) {
|
||||
const [innerToken, _] = findInnerTokenAndParent(pos, root);
|
||||
return innerToken;
|
||||
}
|
||||
|
||||
export function findInnerTokenAndParent(
|
||||
pos: Position,
|
||||
root?: TemplateToken
|
||||
): [TemplateToken | null, TemplateToken | null] {
|
||||
if (!root) {
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
const s = [root];
|
||||
|
||||
let parent: TemplateToken | null = null;
|
||||
|
||||
for (;;) {
|
||||
const token = s.shift();
|
||||
if (!token) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!posInToken(pos, token)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Position is in token, enqueue children if there are any
|
||||
switch (token.templateTokenType) {
|
||||
case MAPPING_TYPE:
|
||||
const mappingToken = token as MappingToken;
|
||||
parent = mappingToken;
|
||||
for (let i = 0; i < mappingToken.count; i++) {
|
||||
const { key, value } = mappingToken.get(i);
|
||||
|
||||
// Null tokens don't have a position, we can only use the line information
|
||||
if (nullNodeOnLine(pos, key, value)) {
|
||||
return [value, key];
|
||||
}
|
||||
|
||||
s.push(value);
|
||||
}
|
||||
continue;
|
||||
|
||||
case SEQUENCE_TYPE:
|
||||
const sequenceToken = token as SequenceToken;
|
||||
parent = sequenceToken;
|
||||
for (let i = 0; i < sequenceToken.count; i++) {
|
||||
s.push(sequenceToken.get(i));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
return [token, parent];
|
||||
}
|
||||
|
||||
return [null, parent];
|
||||
}
|
||||
|
||||
function posInToken(pos: Position, token: TemplateToken): boolean {
|
||||
if (!token.range) {
|
||||
return false;
|
||||
}
|
||||
const r = token.range;
|
||||
|
||||
// TokenRange is one-based, Position is zero-based
|
||||
const tokenLine = pos.line + 1;
|
||||
const tokenChar = pos.character + 1;
|
||||
|
||||
// Check lines
|
||||
if (r.start[0] > tokenLine || tokenLine > r.end[0]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Position is within the token lines. Check character/column if pos line matches
|
||||
// start or end
|
||||
if (
|
||||
(r.start[0] === tokenLine && tokenChar < r.start[1]) ||
|
||||
(r.end[0] === tokenLine && tokenChar > r.end[1])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function nullNodeOnLine(
|
||||
pos: Position,
|
||||
key: TemplateToken,
|
||||
value: TemplateToken
|
||||
): boolean {
|
||||
if (value.templateTokenType !== NULL_TYPE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!value.range) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!key.range) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value.range.start[0] !== value.range.end[0]) {
|
||||
// Token occupies multiple lines, can't be a null node
|
||||
return false;
|
||||
}
|
||||
|
||||
// TokenRange is one-based, Position is zero-based
|
||||
const posLine = pos.line + 1;
|
||||
if (posLine != value.range.start[0]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { Position, TextDocument } from "vscode-languageserver-textdocument";
|
||||
|
||||
const DUMMY_KEY = "dummy";
|
||||
|
||||
// Transform a document to work around YAML parsing issues
|
||||
// Based on `_transform` in https://github.com/cschleiden/github-actions-parser/blob/main/src/lib/parser/complete.ts#L311
|
||||
export function transform(
|
||||
doc: TextDocument,
|
||||
pos: Position
|
||||
): [TextDocument, Position] {
|
||||
const input = doc.getText();
|
||||
let offset = doc.offsetAt(pos);
|
||||
// TODO: Optimize this...
|
||||
const lines = input.split("\n");
|
||||
const lineNo = input
|
||||
.substring(0, offset)
|
||||
.split("")
|
||||
.filter((x) => x === "\n").length;
|
||||
const linePos =
|
||||
offset - lines.slice(0, lineNo).reduce((p, l) => p + l.length + 1, 0);
|
||||
const line = lines[lineNo];
|
||||
|
||||
let partialInput = line.trim();
|
||||
// Special case for Actions, if this line contains an expression marker, do _not_ transform. This is
|
||||
// an ugly fix for auto-completion in multi-line YAML strings. At this point in the process, we cannot
|
||||
// determine if a line is in such a multi-line string.
|
||||
if (partialInput.indexOf("${{") === -1) {
|
||||
const colon = line.indexOf(":");
|
||||
if (colon === -1) {
|
||||
const trimmedLine = line.trim();
|
||||
if (trimmedLine === "" || trimmedLine === "-") {
|
||||
// Node in sequence or empty line
|
||||
let spacer = "";
|
||||
if (trimmedLine === "-" && !line.endsWith(" ")) {
|
||||
spacer = " ";
|
||||
offset++;
|
||||
}
|
||||
|
||||
lines[lineNo] =
|
||||
line.substring(0, linePos) +
|
||||
spacer +
|
||||
DUMMY_KEY +
|
||||
(trimmedLine === "-" ? "" : ":") +
|
||||
line.substring(linePos);
|
||||
|
||||
// Adjust pos by one to prevent a sequence node being marked as active
|
||||
offset++;
|
||||
} else if (!trimmedLine.startsWith("-")) {
|
||||
// Add `:` to end of line
|
||||
lines[lineNo] = line + ":";
|
||||
}
|
||||
|
||||
if (trimmedLine.startsWith("-")) {
|
||||
partialInput = trimmedLine
|
||||
.substring(trimmedLine.indexOf("-") + 1)
|
||||
.trim();
|
||||
}
|
||||
} else {
|
||||
partialInput = (
|
||||
offset > colon ? line.substring(colon + 1) : line.substring(0, colon)
|
||||
).trim();
|
||||
offset = offset - 1;
|
||||
}
|
||||
}
|
||||
const newDoc = TextDocument.create(
|
||||
doc.uri,
|
||||
doc.languageId,
|
||||
doc.version,
|
||||
lines.join("\n")
|
||||
);
|
||||
return [newDoc, newDoc.positionAt(offset)];
|
||||
}
|
||||
Reference in New Issue
Block a user