feat: code blocks beta

This commit is contained in:
obvTiger 2025-03-27 17:28:51 +01:00
parent 362b7aa15e
commit 1ecb6d8682
19 changed files with 756 additions and 94 deletions

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
{"root":["../client/src/extension.ts","../server/src/server.ts"],"version":"5.7.3"}
{"root":["../client/src/extension.ts","../server/src/server.ts"],"version":"5.8.2"}

File diff suppressed because one or more lines are too long

View file

@ -38,6 +38,8 @@ const elements = [
'card', 'badge', 'alert', 'tooltip', 'input', 'textarea', 'select',
'checkbox', 'radio', 'switch', 'list', 'table', 'progress', 'slider'
];
// Script block types
const scriptBlocks = ['client', 'server'];
// Single instance elements
const singleElements = ['page', 'navbar'];
// Blueprint properties
@ -104,6 +106,65 @@ connection.onCompletion((textDocumentPosition) => {
}]
}];
}
// Check for @client or @server block completion
if (linePrefix.trim() === '@' || linePrefix.trim().startsWith('@')) {
return scriptBlocks.map(blockType => ({
label: `@${blockType}`,
kind: node_1.CompletionItemKind.Snippet,
insertText: `@${blockType} {\n $1\n}`,
insertTextFormat: node_1.InsertTextFormat.Snippet,
documentation: `Create a ${blockType} script block`
}));
}
// After an @client or @server block opening brace, suggest JS snippets
const scriptBlockMatch = /@(client|server)\s*{\s*$/.exec(linePrefix);
if (scriptBlockMatch) {
const blockType = scriptBlockMatch[1];
const jsSnippets = [];
if (blockType === 'client') {
jsSnippets.push({
label: 'element.set',
kind: node_1.CompletionItemKind.Method,
insertText: '${1:elementId}.set("${2:new value}");',
insertTextFormat: node_1.InsertTextFormat.Snippet,
documentation: 'Set the content of an element'
}, {
label: 'console.log',
kind: node_1.CompletionItemKind.Method,
insertText: 'console.log("${1:message}");',
insertTextFormat: node_1.InsertTextFormat.Snippet,
documentation: 'Log a message to the console'
}, {
label: 'DOM event handling',
kind: node_1.CompletionItemKind.Snippet,
insertText: 'e.preventDefault();\n${1}',
insertTextFormat: node_1.InsertTextFormat.Snippet,
documentation: 'Prevent default action of event'
});
}
else if (blockType === 'server') {
jsSnippets.push({
label: 'element.set',
kind: node_1.CompletionItemKind.Method,
insertText: '${1:elementId}.set(${2:newValue});',
insertTextFormat: node_1.InsertTextFormat.Snippet,
documentation: 'Update element value from server'
}, {
label: 'element.value',
kind: node_1.CompletionItemKind.Property,
insertText: 'const value = ${1:elementId}.value;',
insertTextFormat: node_1.InsertTextFormat.Snippet,
documentation: 'Get the current value of an element'
}, {
label: 'fetch data',
kind: node_1.CompletionItemKind.Snippet,
insertText: 'const response = await fetch("${1:url}");\nconst data = await response.json();\n${2}',
insertTextFormat: node_1.InsertTextFormat.Snippet,
documentation: 'Fetch data from an API'
});
}
return jsSnippets;
}
// Inside page block
if (text.includes('page {') && !text.includes('}')) {
return pageProperties.map(prop => ({

File diff suppressed because one or more lines are too long

View file

@ -58,6 +58,11 @@ const elements = [
'checkbox', 'radio', 'switch', 'list', 'table', 'progress', 'slider'
];
// Script blocks
const scriptBlocks = [
'client', 'server'
];
// Single instance elements
const singleElements = ['page', 'navbar'];
@ -75,6 +80,9 @@ const properties = [
// Page configuration properties
const pageProperties = ['title', 'description', 'keywords', 'author'];
// ID attribute suggestion - using underscore format
const idAttributeTemplate = 'id:$1_$2';
// Container elements that can have children
const containerElements = [
'horizontal', 'vertical', 'section', 'grid', 'navbar',
@ -114,6 +122,19 @@ connection.onCompletion(
const line = lines[position.line];
const linePrefix = line.slice(0, position.character);
// Suggest script blocks after @ symbol
if (linePrefix.trim().endsWith('@')) {
return scriptBlocks.map(block => ({
label: `@${block}`,
kind: CompletionItemKind.Snippet,
insertText: `@${block} {\n $1\n}`,
insertTextFormat: InsertTextFormat.Snippet,
documentation: block === 'client' ?
'Create a client-side JavaScript block that runs when the element is clicked. The "e" event object is available.' :
'Create a server-side JavaScript block that runs on the server.'
}));
}
// Check if this is a template completion trigger
if (linePrefix.trim() === '!') {
return [{
@ -145,13 +166,22 @@ connection.onCompletion(
}));
}
// After an opening parenthesis, suggest properties
// After an opening parenthesis, suggest properties including ID with underscore format
if (linePrefix.trim().endsWith('(')) {
return properties.map(prop => ({
label: prop,
kind: CompletionItemKind.Property,
documentation: `Apply ${prop} property`
}));
return [
...properties.map(prop => ({
label: prop,
kind: CompletionItemKind.Property,
documentation: `Apply ${prop} property`
})),
{
label: 'id',
kind: CompletionItemKind.Property,
insertText: idAttributeTemplate,
insertTextFormat: InsertTextFormat.Snippet,
documentation: 'Add an ID to the element (use underscores instead of hyphens for JavaScript compatibility)'
}
];
}
// After a container element's opening brace, suggest child elements
@ -173,6 +203,26 @@ connection.onCompletion(
break;
}
// Include client/server block suggestions for interactive elements
if (['button', 'button-light', 'button-secondary', 'button-compact'].includes(parentElement)) {
return [
...suggestedElements.map(element => ({
label: element,
kind: CompletionItemKind.Class,
insertText: `${element} {\n $1\n}`,
insertTextFormat: InsertTextFormat.Snippet,
documentation: `Create a ${element} block inside ${parentElement}`
})),
{
label: '@client',
kind: CompletionItemKind.Snippet,
insertText: `@client {\n $1\n}`,
insertTextFormat: InsertTextFormat.Snippet,
documentation: 'Create a client-side JavaScript block that runs when the element is clicked. The "e" event object is available.'
}
];
}
return suggestedElements.map(element => ({
label: element,
kind: CompletionItemKind.Class,
@ -181,6 +231,27 @@ connection.onCompletion(
documentation: `Create a ${element} block inside ${parentElement}`
}));
}
// Inside interactive elements, suggest @client blocks
const interactiveElementMatch = /\b(button|button-light|button-secondary|button-compact|input|textarea|select|checkbox|radio|switch)\s*(?:\([^)]*\))?\s*{\s*$/.exec(linePrefix);
if (interactiveElementMatch) {
return [
{
label: '@client',
kind: CompletionItemKind.Snippet,
insertText: `@client {\n $1\n}`,
insertTextFormat: InsertTextFormat.Snippet,
documentation: 'Create a client-side JavaScript block that runs when the element is clicked. The "e" event object is available.'
},
{
label: 'text',
kind: CompletionItemKind.Class,
insertText: `"$1"`,
insertTextFormat: InsertTextFormat.Snippet,
documentation: 'Add text content to the element'
}
];
}
// Get available single instance elements
const availableSingleElements = singleElements.filter(element => !elementExists(text, element));

View file

@ -18,6 +18,9 @@
{
"include": "#strings"
},
{
"include": "#script-blocks"
},
{
"include": "#punctuation"
}
@ -64,6 +67,24 @@
}
]
},
"script-blocks": {
"patterns": [
{
"begin": "@(client|server)\\s*\\{",
"end": "\\}",
"beginCaptures": {
"0": { "name": "keyword.control.blueprint" },
"1": { "name": "entity.name.function.blueprint" }
},
"contentName": "source.js.embedded.blueprint",
"patterns": [
{
"include": "source.js"
}
]
}
]
},
"properties": {
"patterns": [
{
@ -71,7 +92,7 @@
"name": "support.type.property-name.blueprint"
},
{
"match": "(?<!:)(src|type|href|\\w+)\\s*:",
"match": "(?<!:)(src|type|href|id|\\w+)\\s*:",
"captures": {
"1": { "name": "support.type.property-name.blueprint" }
}