beta/code-blocks #1

Merged
obvtiger merged 4 commits from beta/code-blocks into main 2025-04-01 15:22:16 +02:00
4 changed files with 74 additions and 69 deletions
Showing only changes of commit 79a7f92ff9 - Show all commits

File diff suppressed because one or more lines are too long

View file

@ -38,8 +38,10 @@ const elements = [
'card', 'badge', 'alert', 'tooltip', 'input', 'textarea', 'select',
'checkbox', 'radio', 'switch', 'list', 'table', 'progress', 'slider'
];
// Script block types
const scriptBlocks = ['client', 'server'];
// Script blocks
const scriptBlocks = [
'client', 'server'
];
// Single instance elements
const singleElements = ['page', 'navbar'];
// Blueprint properties
@ -54,6 +56,8 @@ 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',
@ -87,6 +91,18 @@ connection.onCompletion((textDocumentPosition) => {
const position = textDocumentPosition.position;
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: node_1.CompletionItemKind.Snippet,
insertText: `@${block} {\n $1\n}`,
insertTextFormat: node_1.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 [{
@ -106,65 +122,6 @@ 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 => ({
@ -175,13 +132,22 @@ connection.onCompletion((textDocumentPosition) => {
documentation: `Add ${prop} to the page configuration`
}));
}
// 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: node_1.CompletionItemKind.Property,
documentation: `Apply ${prop} property`
}));
return [
...properties.map(prop => ({
label: prop,
kind: node_1.CompletionItemKind.Property,
documentation: `Apply ${prop} property`
})),
{
label: 'id',
kind: node_1.CompletionItemKind.Property,
insertText: idAttributeTemplate,
insertTextFormat: node_1.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
const containerMatch = /\b(horizontal|vertical|section|grid|navbar|links|card)\s*{\s*$/.exec(linePrefix);
@ -200,6 +166,25 @@ connection.onCompletion((textDocumentPosition) => {
suggestedElements = ['title', 'text', 'button', 'image'];
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: node_1.CompletionItemKind.Class,
insertText: `${element} {\n $1\n}`,
insertTextFormat: node_1.InsertTextFormat.Snippet,
documentation: `Create a ${element} block inside ${parentElement}`
})),
{
label: '@client',
kind: node_1.CompletionItemKind.Snippet,
insertText: `@client {\n $1\n}`,
insertTextFormat: node_1.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: node_1.CompletionItemKind.Class,
@ -208,6 +193,26 @@ connection.onCompletion((textDocumentPosition) => {
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: node_1.CompletionItemKind.Snippet,
insertText: `@client {\n $1\n}`,
insertTextFormat: node_1.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: node_1.CompletionItemKind.Class,
insertText: `"$1"`,
insertTextFormat: node_1.InsertTextFormat.Snippet,
documentation: 'Add text content to the element'
}
];
}
// Get available single instance elements
const availableSingleElements = singleElements.filter(element => !elementExists(text, element));
// Combine regular elements with available single instance elements

File diff suppressed because one or more lines are too long