pomljs
Version:
Prompt Orchestration Markup Language
383 lines (378 loc) • 16.5 kB
JavaScript
import require$$0 from 'lodash';
import require$$1 from '@xml-tools/parser';
import require$$2 from '@xml-tools/common';
var xmlContentAssist;
var hasRequiredXmlContentAssist;
function requireXmlContentAssist () {
if (hasRequiredXmlContentAssist) return xmlContentAssist;
hasRequiredXmlContentAssist = 1;
// Adapted from https://github.com/SAP/xml-tools/blob/master/packages/content-assist/lib/content-assist.js
const { defaultsDeep, forEach, isArray, find, findIndex, flatMap, identity, last, isEmpty } = require$$0; // eslint-disable-line
const { BaseXmlCstVisitor } = require$$1; // eslint-disable-line
const { findNextTextualToken } = require$$2; // eslint-disable-line
function getSuggestions(options) {
const actualOptions = defaultsDeep(options, {
providers: {
elementContent: [],
elementName: [],
elementCloseName: [],
attributeName: [],
attributeValue: [],
},
context: undefined
});
let { providerType, providerArgs } = computeCompletionSyntacticContext({
cst: actualOptions.cst,
tokenVector: actualOptions.tokenVector,
ast: actualOptions.ast,
offset: actualOptions.offset
});
// Inject Additional semantic context for the content assist providers.
providerArgs.context = actualOptions.context;
if (providerType === null) {
return [];
}
else {
const selectedProviders = actualOptions.providers[providerType];
const suggestions = flatMap(selectedProviders, suggestionProvider => suggestionProvider(providerArgs));
return suggestions;
}
}
function computeCompletionSyntacticContext({ cst, ast: docAst, offset, tokenVector }) {
const contextVisitor = new SuggestionContextVisitorWithClose(docAst, offset, tokenVector);
contextVisitor.visit(cst);
return contextVisitor.result;
}
/* eslint-disable no-unused-vars -- consistent signatures in visitor methods even if they are empty placeholders */
class SuggestionContextVisitorWithClose extends BaseXmlCstVisitor {
constructor(docAst, offset, tokenVector) {
super();
this.docAst = docAst;
this.targetOffset = offset;
this.tokenVector = tokenVector;
this.result = { providerType: null, providerArgs: {} };
this.found = false;
}
/**
* @param {DocumentCtx} ctx
*/
document(ctx) {
this.visit(ctx.element, this.docAst.rootElement);
}
/**
* @param {PrologCtx} ctx
*/
/* istanbul ignore next - place holder*/
prolog(ctx, astNode) { }
/**
* @param {docTypeDeclCtx} ctx
*/
/* istanbul ignore next - place holder*/
docTypeDecl(ctx, astNode) { }
/**
* @param {ExternalIDCtx} ctx
*/
/* istanbul ignore next - place holder*/
externalID(ctx, astNode) { }
/**
* @param {ContentCtx} ctx
* @param {XMLElement} astNode
*
*/
content(ctx, astNode) {
forEach(ctx.element, (elem, idx) => {
this.visit(elem, astNode.subElements[idx]);
});
}
/**
* @param {ElementCtx} ctx
* @param {XMLElement} astNode
*/
element(ctx, astNode) {
// Order of handlers can affect the result!
// They are ordered by priority, the more specific handlers are listed first.
handleElementNameWithoutPrefixScenario(ctx, astNode, this);
if (this.found === false) {
handleElementNameWithPrefixScenario(ctx, astNode, this);
}
// Traverse Deeper
if (this.found === false) {
forEach(ctx.attribute, (attrib, idx) => this.visit(attrib, astNode.attributes[idx]));
this.visit(ctx.content, astNode);
}
if (this.found === false) {
handleNewAttributeKeyScenario(ctx, astNode, this.tokenVector, this);
}
if (this.found === false) {
handleElementContentScenario(ctx, astNode, this);
}
if (this.found === false) {
handleElementNameCloseWithoutPrefixScenario(ctx, astNode, this);
}
if (this.found === false) {
handleElementNameCloseWithPrefixScenario(ctx, astNode, this);
}
}
/**
* @param {ReferenceCtx} ctx
*/
/* istanbul ignore next - place holder*/
reference(ctx, astNode) { }
/**
* @param {AttributeCtx} ctx
* @param {XMLAttribute} astNode
*/
attribute(ctx, astNode) {
// potential Attribute Value scenarios
/* istanbul ignore else - Very difficult to reproduce specific partial CSTs */
if (exists(ctx.STRING)) {
const valueTok = ctx.STRING[0];
if (
// The content assist point must be inside the string quotes
valueTok.startOffset < this.targetOffset &&
valueTok.endOffset >= this.targetOffset) {
const prefixEnd = this.targetOffset - valueTok.startOffset;
const prefix = valueTok.image.substring(1, prefixEnd);
this.result.providerType = 'attributeValue';
this.result.providerArgs = {
element: astNode.parent,
attribute: astNode,
prefix: prefix !== '' ? prefix : undefined
};
this.found = true;
}
}
/* istanbul ignore else - Very difficult to reproduce specific partial CSTs */
if (exists(ctx.Name)) {
const keyTok = ctx.Name[0];
if (keyTok.startOffset <= this.targetOffset && keyTok.endOffset + 1 >= this.targetOffset) {
const prefixEnd = this.targetOffset - keyTok.startOffset;
const prefix = keyTok.image.substring(0, prefixEnd);
this.result.providerType = 'attributeName';
this.result.providerArgs = {
element: astNode.parent,
attribute: astNode,
prefix: prefix !== '' ? prefix : undefined
};
this.found = true;
}
}
}
/**
* @param {ChardataCtx} ctx
*/
/* istanbul ignore next - place holder*/
chardata(ctx, astNode) { }
/**
* @param {MiscCtx} ctx
*/
/* istanbul ignore next - place holder*/
misc(ctx, astNode) { }
}
/* eslint-enable no-unused-vars -- see matching pair above */
function handleElementNameWithoutPrefixScenario(ctx, astNode, visitor) {
/* istanbul ignore else - Very difficult to reproduce specific partial CSTs */
if (exists(ctx.OPEN)) {
const openTok = ctx.OPEN[0];
if (openTok.endOffset + 1 === visitor.targetOffset) {
visitor.result.providerType = 'elementName';
visitor.result.providerArgs = { element: astNode, prefix: undefined };
visitor.found = true;
}
}
}
function handleElementNameWithPrefixScenario(ctx, astNode, visitor) {
/* istanbul ignore else - Very difficult to reproduce specific partial CSTs */
if (exists(ctx.Name)) {
const nameTok = ctx.Name[0];
if (nameTok.startOffset < visitor.targetOffset &&
nameTok.endOffset + 1 >= visitor.targetOffset) {
visitor.result.providerType = 'elementName';
const prefixLength = visitor.targetOffset - nameTok.startOffset;
const prefix = nameTok.image.substring(0, prefixLength);
visitor.result.providerArgs = { element: astNode, prefix: prefix };
visitor.found = true;
}
}
}
function handleElementNameCloseWithoutPrefixScenario(ctx, astNode, visitor) {
/* istanbul ignore else - Very difficult to reproduce specific partial CSTs */
if (exists(ctx.SLASH_OPEN)) {
const openTok = ctx.SLASH_OPEN[0];
if (openTok.endOffset + 1 === visitor.targetOffset) {
visitor.result.providerType = 'elementNameClose';
visitor.result.providerArgs = { element: astNode, prefix: undefined };
visitor.found = true;
}
}
}
function handleElementNameCloseWithPrefixScenario(ctx, astNode, visitor) {
/* istanbul ignore else - Very difficult to reproduce specific partial CSTs */
if (exists(ctx.END_NAME)) {
const nameTok = ctx.END_NAME[0];
if (nameTok.startOffset < visitor.targetOffset &&
nameTok.endOffset + 1 >= visitor.targetOffset) {
visitor.result.providerType = 'elementNameClose';
const prefixLength = visitor.targetOffset - nameTok.startOffset;
const prefix = nameTok.image.substring(0, prefixLength);
visitor.result.providerArgs = { element: astNode, prefix: prefix };
visitor.found = true;
}
}
}
function handleNewAttributeKeyScenario(ctx, astNode, tokenVector, visitor) {
// Potential AttributeKey scenario in a completely new attribute
// Note the guard condition (in caller) to avoid this branch if one of the attributes scenarios was already detected.
// This means the order of checking the scenarios is meaningful!
// Example of the problem:
// - `<person gen⇶>` should be matched as attributeName **with prefix** (In attribute handler code)
// - `<person gen="Y"⇶>` should be matched as a **new** attributeName **without prefix** (in the code below).
// But the logic below cannot distinguish these, so it must only be executed if the attribute handler failed.
const attributesRange = { from: undefined, to: undefined };
let hasTerminatedAttribRange = false;
let hasAttributeCloseToken = false;
/* istanbul ignore else - Very difficult to reproduce specific partial CSTs */
if (exists(ctx.Name)) {
attributesRange.from = ctx.Name[0].endOffset + 1;
// Figure where does the attributes area end
if (exists(ctx.START_CLOSE)) {
attributesRange.to = ctx.START_CLOSE[0].startOffset;
hasTerminatedAttribRange = true;
hasAttributeCloseToken = true;
}
else if (exists(ctx.SLASH_CLOSE)) {
attributesRange.to = ctx.SLASH_CLOSE[0].startOffset;
hasTerminatedAttribRange = true;
hasAttributeCloseToken = true;
// If we do not have a proper Attribute Section closing mark we use the last attribute instead
}
else if (isEmpty(ctx.attribute) === false) {
attributesRange.to = last(ctx.attribute).location.endOffset;
hasTerminatedAttribRange = true;
}
}
if (hasTerminatedAttribRange &&
visitor.targetOffset <= attributesRange.to &&
attributesRange.from <= visitor.targetOffset) {
const isNotInExistingAttribute = find(ctx.attribute, attribCst => {
const attribLoc = attribCst.location;
return (visitor.targetOffset >= attribLoc.startOffset &&
visitor.targetOffset <= attribLoc.endOffset);
}) === undefined;
// inside attribute area but not contained in any existing attribute
/* istanbul ignore else - else branch is handled inside the attributes themselves and can never occur here */
if (isNotInExistingAttribute) {
visitor.result.providerType = 'attributeName';
visitor.result.providerArgs = {
element: astNode,
attribute: undefined,
prefix: undefined
};
visitor.found = true;
}
}
else if (
/**
* Heuristic when we some attributes but no proper attribute Range
* and we request the assist after the last attribute.
* <people>
* <person age="66" ⇶
* </people>
*/
hasTerminatedAttribRange &&
visitor.targetOffset > attributesRange.to &&
hasAttributeCloseToken === false) {
handleNewAttributeKeyForPartialElement(
// dummy pesudo token a we are searching the following token using the endOffset
{ endOffset: attributesRange.to }, tokenVector, visitor, astNode);
/**
* Heuristic when we have no attribute range, e.g:
* <people>
* <person ⇶
* </people>
*/
}
else if (hasTerminatedAttribRange === false) {
handleNewAttributeKeyForPartialElement(ctx.Name ? ctx.Name[0] : ctx.OPEN[0], tokenVector, visitor, astNode);
}
}
function handleNewAttributeKeyForPartialElement(possibleAttribKeyRangeStartTok, tokenVector, visitor, astNode) {
const nextAfterElemNameTok = findNextTextualToken(tokenVector, possibleAttribKeyRangeStartTok.endOffset);
if (visitor.targetOffset > possibleAttribKeyRangeStartTok.endOffset &&
(nextAfterElemNameTok === null || // `null`` means there are no more textual tokens in the input
visitor.targetOffset <= nextAfterElemNameTok.startOffset)) {
visitor.result.providerType = 'attributeName';
visitor.result.providerArgs = {
element: astNode,
attribute: undefined,
prefix: undefined
};
visitor.found = true;
}
}
function handleElementContentScenario(ctx, astNode, visitor) {
const contentRange = { from: undefined, to: undefined };
let hasContentRange = false;
/* istanbul ignore else - Very difficult to reproduce specific partial CSTs */
if (exists(ctx.START_CLOSE)) {
contentRange.from = ctx.START_CLOSE[0].endOffset + 1;
// visitor logic only works when the attribute section is properly terminated
/* istanbul ignore else - Very difficult to reproduce specific partial CSTs */
if (exists(ctx.SLASH_OPEN)) {
contentRange.to = ctx.SLASH_OPEN[0].startOffset;
hasContentRange = true;
}
else if (exists(ctx.SLASH_CLOSE)) {
contentRange.to = ctx.SLASH_CLOSE[0].startOffset;
hasContentRange = true;
}
}
if (hasContentRange &&
visitor.targetOffset <= contentRange.to &&
contentRange.from <= visitor.targetOffset) {
const allContentChildren = flatMap(ctx.content[0].children, identity);
const innerContentPart = find(allContentChildren, subContent => {
// Handling either CSTNodes or Tokens
const subContentLoc = subContent.location ? subContent.location : subContent;
// Our offset ranges are Inclusive to Exclusive
// <person>abc⇶</person> --> Not inside the CharData Location, but uses `abc` as prefix
// <person>⇶abc</person> --> Inside the CharData Location, but does not have any prefix
const targetOffsetForPrefix = visitor.targetOffset - 1;
return (targetOffsetForPrefix >= subContentLoc.startOffset &&
targetOffsetForPrefix <= subContentLoc.endOffset);
});
// ElementContent without prefix
if (innerContentPart === undefined) {
visitor.result.providerType = 'elementContent';
visitor.result.providerArgs = {
element: astNode,
textContent: undefined,
prefix: undefined
};
visitor.found = true;
}
else if (innerContentPart.name === 'chardata') {
const textNodeIdx = findIndex(ctx.content[0].children.chardata, innerContentPart);
const textContentsAstNode = astNode.textContents[textNodeIdx];
const prefixEnd = visitor.targetOffset - textContentsAstNode.position.startOffset;
visitor.result.providerType = 'elementContent';
visitor.result.providerArgs = {
element: astNode,
textContent: textContentsAstNode,
prefix: textContentsAstNode.text.substring(0, prefixEnd)
};
visitor.found = true;
}
}
}
function exists(tokArr) {
return isArray(tokArr) && tokArr.length === 1 && tokArr[0].isInsertedInRecovery !== true;
}
xmlContentAssist = {
computeCompletionSyntacticContext: computeCompletionSyntacticContext,
getSuggestions: getSuggestions
};
return xmlContentAssist;
}
export { requireXmlContentAssist as __require };
//# sourceMappingURL=xmlContentAssist.js.map