UNPKG

jexl-extended

Version:

Extended grammar for Javascript Expression Language (JEXL)

226 lines (225 loc) 10.7 kB
"use strict"; /** * Monaco Editor Language Registration for JEXL * Helper functions to register JEXL language support with Monaco Editor */ Object.defineProperty(exports, "__esModule", { value: true }); exports.JEXL_LANGUAGE_ID = void 0; exports.registerJexlLanguage = registerJexlLanguage; exports.isJexlLanguageRegistered = isJexlLanguageRegistered; exports.createJexlEditor = createJexlEditor; const language_configuration_1 = require("./language-configuration"); const monarch_language_1 = require("./monarch-language"); const completion_provider_1 = require("./completion-provider"); exports.JEXL_LANGUAGE_ID = 'jexl'; /** * Registers JEXL language support with Monaco Editor * @param monaco - The Monaco Editor instance */ function registerJexlLanguage(monaco) { // Check if language is already registered using Monaco's API const registeredLanguages = monaco.languages.getLanguages(); const isAlreadyRegistered = registeredLanguages.some((lang) => lang.id === exports.JEXL_LANGUAGE_ID); if (isAlreadyRegistered) { return; } // Register the language monaco.languages.register({ id: exports.JEXL_LANGUAGE_ID, extensions: ['.jexl'], aliases: ['JEXL', 'jexl'], mimetypes: ['text/jexl'] }); // Set language configuration monaco.languages.setLanguageConfiguration(exports.JEXL_LANGUAGE_ID, language_configuration_1.jexlLanguageConfiguration); // Set monarch tokenizer monaco.languages.setMonarchTokensProvider(exports.JEXL_LANGUAGE_ID, monarch_language_1.jexlMonarchLanguage); // Register completion provider monaco.languages.registerCompletionItemProvider(exports.JEXL_LANGUAGE_ID, { provideCompletionItems: function (model, position) { const word = model.getWordUntilPosition(position); const range = { startLineNumber: position.lineNumber, endLineNumber: position.lineNumber, startColumn: word.startColumn, endColumn: word.endColumn }; // Get text before cursor to analyze context const textBeforeCursor = model.getValueInRange({ startLineNumber: position.lineNumber, startColumn: 1, endLineNumber: position.lineNumber, endColumn: position.column }); // Look for the last pipe operator before the cursor const lastPipeIndex = textBeforeCursor.lastIndexOf('|'); const isAfterPipe = lastPipeIndex !== -1 && // Make sure there's no significant content after the pipe (just whitespace and partial word) /^\s*\w*$/.test(textBeforeCursor.substring(lastPipeIndex + 1)); // Check if we're after an identifier (function, variable, etc.) // This regex matches: word characters, closing brackets/parens, or numbers at the end const afterIdentifierMatch = textBeforeCursor.match(/[\w\]\)]\s*$/); const isAfterIdentifier = afterIdentifierMatch !== null && !isAfterPipe; // Check if we're at the start or after an operator const afterOperatorMatch = textBeforeCursor.match(/[+\-*/%^=!<>&|?:,(\[\s]\s*$/); const isAfterOperatorOrStart = afterOperatorMatch !== null || textBeforeCursor.trim() === ''; if (isAfterPipe) { // After pipe: only show transforms const transformItems = (0, completion_provider_1.createJexlTransformItems)(); return { suggestions: transformItems.map(item => ({ ...item, range })) }; } else if (isAfterIdentifier) { // After identifier: suggest operators and pipe const operatorItems = (0, completion_provider_1.createJexlOperators)(); return { suggestions: operatorItems.map(item => ({ ...item, range })) }; } else if (isAfterOperatorOrStart) { // At start or after operator: show functions and keywords const functionItems = (0, completion_provider_1.createJexlFunctionItems)(); const keywordItems = (0, completion_provider_1.createJexlKeywords)(); return { suggestions: [ ...functionItems.map(item => ({ ...item, range })), ...keywordItems.map(item => ({ ...item, range })) ] }; } else { // Fallback: show all functions and keywords const functionItems = (0, completion_provider_1.createJexlFunctionItems)(); const keywordItems = (0, completion_provider_1.createJexlKeywords)(); return { suggestions: [ ...functionItems.map(item => ({ ...item, range })), ...keywordItems.map(item => ({ ...item, range })) ] }; } } }); // Register hover provider monaco.languages.registerHoverProvider(exports.JEXL_LANGUAGE_ID, { provideHover: function (model, position) { const word = model.getWordAtPosition(position); if (!word) return; // Check if we're after a pipe operator to determine context const textBeforeCursor = model.getValueInRange({ startLineNumber: position.lineNumber, startColumn: 1, endLineNumber: position.lineNumber, endColumn: word.startColumn }); // Look for the last pipe operator before this word const lastPipeIndex = textBeforeCursor.lastIndexOf('|'); const isAfterPipe = lastPipeIndex !== -1 && // Make sure there's no significant content after the pipe (just whitespace) /^\s*$/.test(textBeforeCursor.substring(lastPipeIndex + 1)); // Determine preferred type based on context const preferredType = isAfterPipe ? 'transform' : 'function'; // Try to get documentation for function/transform const doc = (0, completion_provider_1.getJexlCompletionDoc)(word.word, preferredType); if (doc) { // Create formatted markdown content const markdownContent = `**${doc.label}** (${doc.type}) - ${doc.detail}\n\n${doc.documentation}`; return { range: new monaco.Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn), contents: [ { value: markdownContent } ] }; } // If no function/transform found, try operators const operatorDoc = (0, completion_provider_1.getOperatorDoc)(word.word); if (operatorDoc) { return { range: new monaco.Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn), contents: [ { value: `**${operatorDoc.label}** - ${operatorDoc.detail}\n\n${operatorDoc.documentation}` } ] }; } } }); // Register folding provider for arrays and objects monaco.languages.registerFoldingRangeProvider(exports.JEXL_LANGUAGE_ID, { provideFoldingRanges: function (model) { const ranges = []; const text = model.getValue(); const lines = text.split('\n'); for (let i = 0; i < lines.length; i++) { const line = lines[i]; // Find opening brackets/braces for (let j = 0; j < line.length; j++) { if (line[j] === '[' || line[j] === '{') { const openChar = line[j]; const closeChar = openChar === '[' ? ']' : '}'; // Find matching closing bracket/brace let depth = 1; let found = false; for (let k = i; k < lines.length && !found; k++) { const startPos = k === i ? j + 1 : 0; const searchLine = lines[k]; for (let l = startPos; l < searchLine.length; l++) { if (searchLine[l] === openChar) { depth++; } else if (searchLine[l] === closeChar) { depth--; if (depth === 0) { // Found matching closing bracket if (k > i) { // Only fold if spans multiple lines ranges.push({ start: i + 1, end: k + 1, kind: monaco.languages.FoldingRangeKind.Region }); } found = true; break; } } } } } } } return ranges; } }); } /** * Checks if JEXL language has been registered * @param monaco - The Monaco Editor instance * @returns true if language is already registered */ function isJexlLanguageRegistered(monaco) { const registeredLanguages = monaco.languages.getLanguages(); return registeredLanguages.some((lang) => lang.id === exports.JEXL_LANGUAGE_ID); } /** * Creates a Monaco Editor instance with JEXL language support * @param monaco - The Monaco Editor instance * @param container - The DOM element to mount the editor * @param options - Editor options * @returns Monaco Editor instance */ function createJexlEditor(monaco, container, options = {}) { // Ensure JEXL language is registered (safe to call multiple times) registerJexlLanguage(monaco); return monaco.editor.create(container, { language: exports.JEXL_LANGUAGE_ID, theme: 'vs-dark', automaticLayout: true, minimap: { enabled: false }, scrollBeyondLastLine: false, fontSize: 14, lineNumbers: 'on', folding: true, ...options }); }