mirror of
https://github.com/h-sphere/sql-seal.git
synced 2026-07-22 10:10:28 +00:00
Merge e488c22278 into 8972c9c7f4
This commit is contained in:
commit
efff4cb2bb
7 changed files with 256 additions and 274 deletions
12
.changeset/nunjucks-template-renderer.md
Normal file
12
.changeset/nunjucks-template-renderer.md
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
"sqlseal": minor
|
||||
---
|
||||
|
||||
Replace Handlebars with Nunjucks in TEMPLATE renderer
|
||||
|
||||
Swap the template engine from Handlebars to Nunjucks for richer template capabilities.
|
||||
Nunjucks provides native groupby/unique filters, macros, template inheritance,
|
||||
set/variables, and include/import directives without SQL workarounds.
|
||||
|
||||
Breaking change: existing templates using Handlebars syntax ({{#each}}, {{#if}}, {{#unless}})
|
||||
must be updated to Nunjucks equivalents ({% for %}, {% if %}, {% set %}).
|
||||
|
|
@ -64,7 +64,7 @@
|
|||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||
"esprima": "^4.0.1",
|
||||
"estraverse": "^5.3.0",
|
||||
"handlebars": "^4.7.8",
|
||||
"nunjucks": "^3.2.4",
|
||||
"json5": "^2.2.3",
|
||||
"jsonpath": "^1.1.1",
|
||||
"lodash": "^4.17.21",
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ export const SQLSealLangDefinition = (views: ViewDefinition[], flags: readonly F
|
|||
ViewExpression = ${viewsDefinitions}
|
||||
ExtraFlags = ${flagsDefinitions}
|
||||
anyObject = "{" (~selectKeyword any)*
|
||||
handlebarsTemplate = (~selectKeyword any)*
|
||||
nunjucksTemplate = (~selectKeyword any)*
|
||||
javascriptTemplate = (~selectKeyword any)*
|
||||
selectKeyword = (caseInsensitive<"WITH"> | caseInsensitive<"SELECT">) &(space | nl | end)
|
||||
tableKeyword = caseInsensitive<"TABLE">
|
||||
|
|
|
|||
|
|
@ -1,60 +1,91 @@
|
|||
// This is renderer for a very basic List view.
|
||||
import { App } from "obsidian";
|
||||
import { RendererConfig, RendererContext } from "./rendererRegistry";
|
||||
import { displayError } from "../../../utils/ui";
|
||||
import Handlebars from "handlebars";
|
||||
import nunjucks from "nunjucks";
|
||||
import { ViewDefinition } from "../parser";
|
||||
import { ParseResults } from "../../syntaxHighlight/cellParser/parseResults";
|
||||
|
||||
const env = new nunjucks.Environment(null, { autoescape: false });
|
||||
|
||||
// Register custom filters
|
||||
env.addFilter("groupby", (arr: any[], key: string) => {
|
||||
const groups: Record<string, any[]> = {};
|
||||
for (const item of arr) {
|
||||
const groupKey = String(item[key] ?? "");
|
||||
groups[groupKey] ??= [];
|
||||
groups[groupKey].push(item);
|
||||
}
|
||||
return Object.entries(groups).map(([k, items]) => ({
|
||||
grouper: k,
|
||||
list: items,
|
||||
}));
|
||||
});
|
||||
|
||||
env.addFilter("unique", (arr: any[], key?: string) => {
|
||||
if (!key) return [...new Set(arr)];
|
||||
const seen = new Set<string>();
|
||||
return arr.filter((item) => {
|
||||
const val = String(item[key] ?? "");
|
||||
if (seen.has(val)) return false;
|
||||
seen.add(val);
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
interface TemplateRendererConfig {
|
||||
template: HandlebarsTemplateDelegate
|
||||
template: nunjucks.Template;
|
||||
}
|
||||
|
||||
export class TemplateRenderer implements RendererConfig {
|
||||
constructor(private readonly app: App) {
|
||||
}
|
||||
constructor(private readonly app: App) {}
|
||||
|
||||
get rendererKey() {
|
||||
return 'template'
|
||||
return "template";
|
||||
}
|
||||
|
||||
get viewDefinition(): ViewDefinition {
|
||||
return {
|
||||
name: this.rendererKey,
|
||||
argument: 'handlebarsTemplate?',
|
||||
singleLine: false
|
||||
}
|
||||
argument: "nunjucksTemplate?",
|
||||
singleLine: false,
|
||||
};
|
||||
}
|
||||
|
||||
validateConfig(config: string): TemplateRendererConfig {
|
||||
if (!config) {
|
||||
return {
|
||||
template: Handlebars.compile('No template Provided')
|
||||
}
|
||||
template: nunjucks.compile("No template provided", env),
|
||||
};
|
||||
}
|
||||
return {
|
||||
template: Handlebars.compile(config)
|
||||
}
|
||||
template: nunjucks.compile(config, env),
|
||||
};
|
||||
}
|
||||
|
||||
render(config: TemplateRendererConfig, el: HTMLElement, { cellParser }: RendererContext) {
|
||||
render(
|
||||
config: TemplateRendererConfig,
|
||||
el: HTMLElement,
|
||||
{ cellParser }: RendererContext,
|
||||
) {
|
||||
return {
|
||||
render: ({ columns, data, frontmatter }: any) => {
|
||||
el.empty()
|
||||
|
||||
const parser = new ParseResults(cellParser!, (el) => new Handlebars.SafeString(el.outerHTML))
|
||||
el.empty();
|
||||
|
||||
// Seems to be the only way to render handlebars into DOM. Don't like it but what can we do.
|
||||
el.innerHTML = config.template({
|
||||
const parser = new ParseResults(
|
||||
cellParser!,
|
||||
(el) => new nunjucks.runtime.SafeString(el.outerHTML),
|
||||
);
|
||||
|
||||
el.innerHTML = config.template.render({
|
||||
data: parser.parse(data, columns),
|
||||
columns,
|
||||
properties: frontmatter
|
||||
})
|
||||
parser.initialise(el)
|
||||
properties: frontmatter,
|
||||
});
|
||||
parser.initialise(el);
|
||||
},
|
||||
error: (error: string) => {
|
||||
displayError(el, error)
|
||||
}
|
||||
}
|
||||
displayError(el, error);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,243 +0,0 @@
|
|||
interface Decorator {
|
||||
type: 'identifier' | 'literal' | 'parameter' | 'comment' | 'keyword' | 'function' | 'error' | 'template-keyword';
|
||||
start: number;
|
||||
end: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlights Handlebars syntax in a template string using regex patterns
|
||||
* @param template - The Handlebars template to highlight
|
||||
* @returns Array of decorator objects for syntax highlighting
|
||||
*/
|
||||
function highlightHandlebars(template: string): Decorator[] {
|
||||
const decorators: Decorator[] = [];
|
||||
|
||||
// Define Handlebars keywords
|
||||
const keywords = ['if', 'else', 'unless', 'each', 'with', 'as', 'in', 'let', 'log', 'lookup'];
|
||||
|
||||
// Helper function to add a decorator
|
||||
function addDecorator(type: Decorator['type'], start: number, end: number): void {
|
||||
// Skip if out of bounds
|
||||
if (start < 0 || end > template.length || start >= end) return;
|
||||
|
||||
decorators.push({ type, start, end });
|
||||
}
|
||||
|
||||
// 1. Find all block comments: {{!-- comment --}}
|
||||
const blockCommentRegex = /{{!--([\s\S]*?)--}}/g;
|
||||
let match: RegExpExecArray | null;
|
||||
|
||||
while ((match = blockCommentRegex.exec(template)) !== null) {
|
||||
addDecorator('comment', match.index, match.index + match[0].length);
|
||||
}
|
||||
|
||||
// 2. Find all inline comments: {{! comment }}
|
||||
const inlineCommentRegex = /{{!((?!--)[^}])*}}/g;
|
||||
|
||||
while ((match = inlineCommentRegex.exec(template)) !== null) {
|
||||
addDecorator('comment', match.index, match.index + match[0].length);
|
||||
}
|
||||
|
||||
// 3. Find all triple mustaches: {{{unescaped}}}
|
||||
const tripleRegex = /{{{([^}]+)}}}/g;
|
||||
|
||||
while ((match = tripleRegex.exec(template)) !== null) {
|
||||
// Highlight the opening and closing braces as keywords
|
||||
addDecorator('template-keyword', match.index, match.index + 3);
|
||||
addDecorator('template-keyword', match.index + match[0].length - 3, match.index + match[0].length);
|
||||
|
||||
// Highlight the content as an identifier
|
||||
const contentStart = match.index + 3;
|
||||
const contentEnd = match.index + match[0].length - 3;
|
||||
addDecorator('identifier', contentStart, contentEnd);
|
||||
}
|
||||
|
||||
// 4. Find all block expressions: {{#helper}}...{{/helper}}
|
||||
const blockStartRegex = /{{#([^}]+)}}/g;
|
||||
|
||||
while ((match = blockStartRegex.exec(template)) !== null) {
|
||||
// Highlight the opening braces and # as keywords
|
||||
addDecorator('template-keyword', match.index, match.index + 2); // {{
|
||||
addDecorator('template-keyword', match.index + 2, match.index + 3); // #
|
||||
addDecorator('template-keyword', match.index + match[0].length - 2, match.index + match[0].length); // }}
|
||||
|
||||
// Process the helper content
|
||||
const helperContent = match[1].trim();
|
||||
const helperStart = match.index + 3;
|
||||
|
||||
// Identify helper name
|
||||
const helperNameMatch = /^([^\s]+)/.exec(helperContent);
|
||||
if (helperNameMatch) {
|
||||
const helperName = helperNameMatch[1];
|
||||
const helperNameEnd = helperStart + helperName.length;
|
||||
|
||||
// Check if it's a keyword or function
|
||||
if (keywords.includes(helperName)) {
|
||||
addDecorator('template-keyword', helperStart, helperNameEnd);
|
||||
} else {
|
||||
addDecorator('function', helperStart, helperNameEnd);
|
||||
}
|
||||
|
||||
// Process params - this is simplified
|
||||
const paramsStr = helperContent.substring(helperName.length).trim();
|
||||
if (paramsStr.length > 0) {
|
||||
const paramStart = helperNameEnd + (helperContent.substring(helperName.length).length - paramsStr.length);
|
||||
processParams(paramsStr, paramStart, decorators);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Find all closing block expressions: {{/helper}}
|
||||
const blockEndRegex = /{{\/([^}]+)}}/g;
|
||||
|
||||
while ((match = blockEndRegex.exec(template)) !== null) {
|
||||
// Highlight the opening braces and / as keywords
|
||||
addDecorator('template-keyword', match.index, match.index + 2); // {{
|
||||
addDecorator('template-keyword', match.index + 2, match.index + 3); // /
|
||||
addDecorator('template-keyword', match.index + match[0].length - 2, match.index + match[0].length); // }}
|
||||
|
||||
// Highlight the helper name
|
||||
const helperName = match[1].trim();
|
||||
const helperStart = match.index + 3;
|
||||
const helperEnd = helperStart + helperName.length;
|
||||
|
||||
if (keywords.includes(helperName)) {
|
||||
addDecorator('template-keyword', helperStart, helperEnd);
|
||||
} else {
|
||||
addDecorator('function', helperStart, helperEnd);
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Find all partials: {{> partial}}
|
||||
const partialRegex = /{{>([^}]+)}}/g;
|
||||
|
||||
while ((match = partialRegex.exec(template)) !== null) {
|
||||
// Highlight the opening braces and > as keywords
|
||||
addDecorator('template-keyword', match.index, match.index + 2); // {{
|
||||
addDecorator('template-keyword', match.index + 2, match.index + 3); // >
|
||||
addDecorator('template-keyword', match.index + match[0].length - 2, match.index + match[0].length); // }}
|
||||
|
||||
// Highlight the partial name
|
||||
const partialContent = match[1].trim();
|
||||
const partialStart = match.index + 3;
|
||||
|
||||
// Identify partial name
|
||||
const partialNameMatch = /^([^\s]+)/.exec(partialContent);
|
||||
if (partialNameMatch) {
|
||||
const partialName = partialNameMatch[1];
|
||||
const partialNameEnd = partialStart + partialName.length;
|
||||
|
||||
addDecorator('identifier', partialStart, partialNameEnd);
|
||||
|
||||
// Process params - this is simplified
|
||||
const paramsStr = partialContent.substring(partialName.length).trim();
|
||||
if (paramsStr.length > 0) {
|
||||
const paramStart = partialNameEnd + (partialContent.substring(partialName.length).length - paramsStr.length);
|
||||
processParams(paramsStr, paramStart, decorators);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 7. Find all regular mustaches: {{expression}}
|
||||
const regularRegex = /{{([^#/!>]([^}]+))}}/g;
|
||||
|
||||
while ((match = regularRegex.exec(template)) !== null) {
|
||||
// Highlight the opening and closing braces as keywords
|
||||
addDecorator('template-keyword', match.index, match.index + 2);
|
||||
addDecorator('template-keyword', match.index + match[0].length - 2, match.index + match[0].length);
|
||||
|
||||
// Process the content
|
||||
const expressionContent = match[1].trim();
|
||||
const expressionStart = match.index + 2;
|
||||
|
||||
// Simple expression like {{variable}}
|
||||
if (!/\s/.test(expressionContent)) {
|
||||
addDecorator('identifier', expressionStart, expressionStart + expressionContent.length);
|
||||
} else {
|
||||
// Handle helpers with params
|
||||
const helperMatch = /^([^\s]+)/.exec(expressionContent);
|
||||
if (helperMatch) {
|
||||
const helperName = helperMatch[1];
|
||||
const helperNameEnd = expressionStart + helperName.length;
|
||||
|
||||
if (keywords.includes(helperName)) {
|
||||
addDecorator('template-keyword', expressionStart, helperNameEnd);
|
||||
} else {
|
||||
addDecorator('identifier', expressionStart, helperNameEnd);
|
||||
}
|
||||
|
||||
// Process params
|
||||
const paramsStr = expressionContent.substring(helperName.length).trim();
|
||||
if (paramsStr.length > 0) {
|
||||
const paramStart = helperNameEnd + (expressionContent.substring(helperName.length).length - paramsStr.length);
|
||||
processParams(paramsStr, paramStart, decorators);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort decorators by start position
|
||||
decorators.sort((a, b) => a.start - b.start);
|
||||
|
||||
return decorators;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process parameters in an expression
|
||||
*/
|
||||
function processParams(paramsStr: string, startPos: number, decorators: Decorator[]): void {
|
||||
// Helper function to add a decorator
|
||||
function addDecorator(type: Decorator['type'], start: number, end: number): void {
|
||||
decorators.push({ type, start, end });
|
||||
}
|
||||
|
||||
// Find string literals
|
||||
const stringRegex = /"([^"\\]*(\\.[^"\\]*)*)"|'([^'\\]*(\\.[^'\\]*)*)'/g;
|
||||
let match: RegExpExecArray | null;
|
||||
let lastPos = 0;
|
||||
|
||||
// First pass: Handle string literals
|
||||
while ((match = stringRegex.exec(paramsStr)) !== null) {
|
||||
const literalStart = startPos + match.index;
|
||||
const literalEnd = literalStart + match[0].length;
|
||||
|
||||
addDecorator('literal', literalStart, literalEnd);
|
||||
lastPos = match.index + match[0].length;
|
||||
}
|
||||
|
||||
// Second pass: Handle parameters
|
||||
const paramRegex = /\s+([^\s"'=]+)=?/g;
|
||||
|
||||
while ((match = paramRegex.exec(paramsStr)) !== null) {
|
||||
const paramStart = startPos + match.index + match[0].indexOf(match[1]);
|
||||
const paramEnd = paramStart + match[1].length;
|
||||
|
||||
// Don't highlight if this is inside a previously detected string literal
|
||||
const isInLiteral = decorators.some(d =>
|
||||
d.type === 'literal' && paramStart >= d.start && paramEnd <= d.end
|
||||
);
|
||||
|
||||
if (!isInLiteral) {
|
||||
addDecorator('parameter', paramStart, paramEnd);
|
||||
}
|
||||
}
|
||||
|
||||
// Find non-literal parameters (simplified)
|
||||
const simpleParamRegex = /([^\s"'=]+)/g;
|
||||
|
||||
while ((match = simpleParamRegex.exec(paramsStr)) !== null) {
|
||||
const paramStart = startPos + match.index;
|
||||
const paramEnd = paramStart + match[0].length;
|
||||
|
||||
// Don't highlight if this is inside a previously detected string literal or parameter
|
||||
const isAlreadyHighlighted = decorators.some(d =>
|
||||
paramStart >= d.start && paramEnd <= d.end
|
||||
);
|
||||
|
||||
if (!isAlreadyHighlighted) {
|
||||
addDecorator('parameter', paramStart, paramEnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default highlightHandlebars;
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
interface Decorator {
|
||||
type:
|
||||
| "identifier"
|
||||
| "literal"
|
||||
| "parameter"
|
||||
| "comment"
|
||||
| "keyword"
|
||||
| "function"
|
||||
| "error"
|
||||
| "template-keyword";
|
||||
start: number;
|
||||
end: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlights Nunjucks syntax in a template string using regex patterns.
|
||||
* Supports: {{ expressions }}, {% tags %}, {# comments #}, and | filters.
|
||||
*/
|
||||
function highlightNunjucks(template: string): Decorator[] {
|
||||
const decorators: Decorator[] = [];
|
||||
let match: RegExpExecArray | null;
|
||||
|
||||
const keywords = [
|
||||
"if",
|
||||
"elif",
|
||||
"else",
|
||||
"endif",
|
||||
"for",
|
||||
"endfor",
|
||||
"in",
|
||||
"block",
|
||||
"endblock",
|
||||
"extends",
|
||||
"include",
|
||||
"import",
|
||||
"from",
|
||||
"macro",
|
||||
"endmacro",
|
||||
"call",
|
||||
"endcall",
|
||||
"set",
|
||||
"endset",
|
||||
"filter",
|
||||
"endfilter",
|
||||
"raw",
|
||||
"endraw",
|
||||
"as",
|
||||
"with",
|
||||
"not",
|
||||
"and",
|
||||
"or",
|
||||
"is",
|
||||
"true",
|
||||
"false",
|
||||
"none",
|
||||
"null",
|
||||
];
|
||||
|
||||
function add(
|
||||
type: Decorator["type"],
|
||||
start: number,
|
||||
end: number,
|
||||
): void {
|
||||
if (start >= 0 && end <= template.length && start < end) {
|
||||
decorators.push({ type, start, end });
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Comments: {# ... #}
|
||||
const commentRegex = /\{#[\s\S]*?#\}/g;
|
||||
while ((match = commentRegex.exec(template)) !== null) {
|
||||
add("comment", match.index, match.index + match[0].length);
|
||||
}
|
||||
|
||||
// 2. Block tags: {% ... %}
|
||||
const blockTagRegex = /\{%[-~]?\s*([\s\S]*?)\s*[-~]?%\}/g;
|
||||
while ((match = blockTagRegex.exec(template)) !== null) {
|
||||
const fullStart = match.index;
|
||||
const fullEnd = fullStart + match[0].length;
|
||||
|
||||
// Delimiters
|
||||
add("template-keyword", fullStart, fullStart + 2);
|
||||
add("template-keyword", fullEnd - 2, fullEnd);
|
||||
|
||||
// Tag content
|
||||
const content = match[1];
|
||||
const contentStart =
|
||||
fullStart + match[0].indexOf(content);
|
||||
|
||||
// First word is the tag keyword
|
||||
const tagMatch = /^([^\s]+)/.exec(content);
|
||||
if (tagMatch) {
|
||||
const tagName = tagMatch[1];
|
||||
const tagEnd = contentStart + tagName.length;
|
||||
|
||||
if (keywords.includes(tagName)) {
|
||||
add("template-keyword", contentStart, tagEnd);
|
||||
} else {
|
||||
add("function", contentStart, tagEnd);
|
||||
}
|
||||
|
||||
// Process remaining content
|
||||
const rest = content.substring(tagName.length).trim();
|
||||
if (rest.length > 0) {
|
||||
const restStart =
|
||||
contentStart +
|
||||
content.indexOf(rest, tagName.length);
|
||||
processExpression(rest, restStart);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Expression tags: {{ ... }}
|
||||
const exprRegex = /\{\{[-~]?\s*([\s\S]*?)\s*[-~]?\}\}/g;
|
||||
while ((match = exprRegex.exec(template)) !== null) {
|
||||
const fullStart = match.index;
|
||||
const fullEnd = fullStart + match[0].length;
|
||||
|
||||
// Delimiters
|
||||
add("template-keyword", fullStart, fullStart + 2);
|
||||
add("template-keyword", fullEnd - 2, fullEnd);
|
||||
|
||||
// Expression content
|
||||
const content = match[1];
|
||||
const contentStart =
|
||||
fullStart + match[0].indexOf(content);
|
||||
processExpression(content, contentStart);
|
||||
}
|
||||
|
||||
function processExpression(expr: string, offset: number): void {
|
||||
// String literals
|
||||
const stringRegex =
|
||||
/"([^"\\]*(\\.[^"\\]*)*)"|'([^'\\]*(\\.[^'\\]*)*)'/g;
|
||||
let m: RegExpExecArray | null;
|
||||
while ((m = stringRegex.exec(expr)) !== null) {
|
||||
add("literal", offset + m.index, offset + m.index + m[0].length);
|
||||
}
|
||||
|
||||
// Number literals
|
||||
const numRegex = /\b(\d+(?:\.\d+)?)\b/g;
|
||||
while ((m = numRegex.exec(expr)) !== null) {
|
||||
add("literal", offset + m.index, offset + m.index + m[0].length);
|
||||
}
|
||||
|
||||
// Filter pipe operator and filter names
|
||||
const filterRegex = /\|\s*(\w+)/g;
|
||||
while ((m = filterRegex.exec(expr)) !== null) {
|
||||
// Highlight the pipe
|
||||
add("template-keyword", offset + m.index, offset + m.index + 1);
|
||||
// Highlight the filter name
|
||||
const filterStart = offset + m.index + m[0].indexOf(m[1]);
|
||||
add("function", filterStart, filterStart + m[1].length);
|
||||
}
|
||||
|
||||
// Keywords within expressions
|
||||
const wordRegex = /\b(\w+)\b/g;
|
||||
while ((m = wordRegex.exec(expr)) !== null) {
|
||||
const word = m[1];
|
||||
const wordStart = offset + m.index;
|
||||
const wordEnd = wordStart + word.length;
|
||||
|
||||
// Skip if already decorated
|
||||
const alreadyDecorated = decorators.some(
|
||||
(d) => wordStart >= d.start && wordEnd <= d.end,
|
||||
);
|
||||
if (alreadyDecorated) continue;
|
||||
|
||||
if (keywords.includes(word)) {
|
||||
add("template-keyword", wordStart, wordEnd);
|
||||
} else if (/^\d/.test(word)) {
|
||||
// Skip numbers (already handled)
|
||||
} else {
|
||||
add("identifier", wordStart, wordEnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decorators.sort((a, b) => a.start - b.start);
|
||||
return decorators;
|
||||
}
|
||||
|
||||
export default highlightNunjucks;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import * as ohm from 'ohm-js';
|
||||
import { cstVisitor, Literal, parse } from 'sql-parser-cst';
|
||||
import highlightHandlebars from './highlighter/handlebarsHighlighter';
|
||||
import highlightNunjucks from './highlighter/nunjucksHighlighter';
|
||||
import { highlightJavaScript } from './highlighter/jsHighlighter';
|
||||
|
||||
const nodes = new Map([
|
||||
|
|
@ -91,10 +91,10 @@ export const highlighterOperation = (grammar: ohm.Grammar) => {
|
|||
end: node.source.endIdx
|
||||
}]
|
||||
},
|
||||
handlebarsTemplate(_node) {
|
||||
nunjucksTemplate(_node) {
|
||||
try {
|
||||
const template = this.source.contents
|
||||
const sections = highlightHandlebars(template)
|
||||
const sections = highlightNunjucks(template)
|
||||
const offset = this.source.startIdx
|
||||
return sections.map(s => ({
|
||||
...s,
|
||||
|
|
|
|||
Loading…
Reference in a new issue