Refactor jqhtml integration to use jqhtml.boot() and migrate blade highlighting to jqhtml extension
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -149,13 +149,13 @@ class JqhtmlBladeCompiler
|
||||
}
|
||||
|
||||
// Build HTML attributes string
|
||||
// Handle class attribute specially to merge with Component_Init
|
||||
$class_value = 'Component_Init';
|
||||
// Handle class attribute specially to merge with _Component_Init
|
||||
$class_value = '_Component_Init';
|
||||
if (isset($html_attrs['class'])) {
|
||||
if ($html_attrs['class']['type'] === 'expression') {
|
||||
$class_value = "Component_Init ' . {$html_attrs['class']['value']} . '";
|
||||
$class_value = "_Component_Init ' . {$html_attrs['class']['value']} . '";
|
||||
} else {
|
||||
$class_value = 'Component_Init ' . $html_attrs['class']['value'];
|
||||
$class_value = '_Component_Init ' . $html_attrs['class']['value'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,13 +229,13 @@ class JqhtmlBladeCompiler
|
||||
}
|
||||
|
||||
// Build HTML attributes string
|
||||
// Handle class attribute specially to merge with Component_Init
|
||||
$class_value = 'Component_Init';
|
||||
// Handle class attribute specially to merge with _Component_Init
|
||||
$class_value = '_Component_Init';
|
||||
if (isset($html_attrs['class'])) {
|
||||
if ($html_attrs['class']['type'] === 'expression') {
|
||||
$class_value = "Component_Init ' . {$html_attrs['class']['value']} . '";
|
||||
$class_value = "_Component_Init ' . {$html_attrs['class']['value']} . '";
|
||||
} else {
|
||||
$class_value = 'Component_Init ' . $html_attrs['class']['value'];
|
||||
$class_value = '_Component_Init ' . $html_attrs['class']['value'];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,55 +1,26 @@
|
||||
/**
|
||||
* JQHTML Integration - Component Hydration System
|
||||
* JQHTML Integration - Component Registration and Hydration Bootstrap
|
||||
*
|
||||
* This module bridges server-rendered HTML and client-side jqhtml components.
|
||||
*
|
||||
* == THE HYDRATION PROBLEM ==
|
||||
*
|
||||
* When PHP/Blade renders a page, jqhtml components appear as placeholder elements:
|
||||
*
|
||||
* Blade source: <User_Card $name="John" />
|
||||
* Rendered HTML: <div class="Component_Init"
|
||||
* data-component-init-name="User_Card"
|
||||
* data-component-args='{"name":"John"}'>
|
||||
* </div>
|
||||
*
|
||||
* These are just div tags - they have no behavior until JavaScript runs.
|
||||
* "Hydration" is the process of finding these placeholders and converting them
|
||||
* into live, interactive jqhtml components.
|
||||
* This module bridges RSpade's manifest system with jqhtml's component runtime.
|
||||
*
|
||||
* == TWO-PHASE INITIALIZATION ==
|
||||
*
|
||||
* Phase 1: _on_framework_modules_define() - Component Registration
|
||||
* - Runs early in framework boot, before DOM is processed
|
||||
* - Registers all ES6 classes extending Component with jqhtml runtime
|
||||
* - Tags static methods with cache IDs for jqhtml's caching system
|
||||
* - After this phase, jqhtml knows: "User_Card" → UserCardClass
|
||||
*
|
||||
* Phase 2: _on_framework_modules_init() - DOM Hydration
|
||||
* - Runs after templates are loaded
|
||||
* - Finds all .Component_Init elements in the DOM
|
||||
* - Extracts component name and args from data attributes
|
||||
* - Calls $element.component(name, args) to hydrate each one
|
||||
* - Recursively processes nested components
|
||||
* - Calls jqhtml.boot() to hydrate all ._Component_Init placeholders
|
||||
* - Triggers 'jqhtml_ready' when all components are initialized
|
||||
*
|
||||
* == THE TRANSFORMATION ==
|
||||
*
|
||||
* Before hydration:
|
||||
* <div class="Component_Init" data-component-init-name="User_Card" ...>
|
||||
*
|
||||
* After hydration:
|
||||
* <div class="User_Card"> ← Component_Init removed, component class added
|
||||
* [component template content] ← Template rendered into element
|
||||
* </div>
|
||||
*
|
||||
* The element is now a live component with event handlers, state, and lifecycle.
|
||||
*
|
||||
* == KEY PARTICIPANTS ==
|
||||
*
|
||||
* JqhtmlBladeCompiler.php - Transforms <Component /> tags into .Component_Init divs
|
||||
* JqhtmlBladeCompiler.php - Transforms <Component /> tags into ._Component_Init divs
|
||||
* jqhtml runtime - Maintains registry of component names → classes
|
||||
* This module - Orchestrates registration and hydration
|
||||
* $().component() - jQuery plugin that creates component instances
|
||||
* jqhtml.boot() - Finds and hydrates all ._Component_Init placeholders
|
||||
* This module - Orchestrates registration and triggers hydration
|
||||
*/
|
||||
class Jqhtml_Integration {
|
||||
/**
|
||||
@@ -120,165 +91,20 @@ class Jqhtml_Integration {
|
||||
/**
|
||||
* Phase 2: DOM Hydration
|
||||
*
|
||||
* Finds all .Component_Init placeholders and converts them into live components.
|
||||
* Delegates to jqhtml.boot() which finds all ._Component_Init placeholders
|
||||
* and converts them into live components.
|
||||
*
|
||||
* == HYDRATION PROCESS ==
|
||||
*
|
||||
* For each .Component_Init element:
|
||||
*
|
||||
* 1. EXTRACT: Read data-component-init-name and data-component-args
|
||||
* 2. CLEANUP: Remove data attributes (prevents re-hydration, hides implementation)
|
||||
* 3. CAPTURE: Save innerHTML for slot/content() processing
|
||||
* 4. INSTANTIATE: Call $element.component(name, args)
|
||||
* 5. RECURSE: After render, hydrate any nested .Component_Init elements
|
||||
*
|
||||
* == NESTED COMPONENT HANDLING ==
|
||||
*
|
||||
* Components can contain other components in their Blade output:
|
||||
*
|
||||
* <User_Card> ← Parent hydrates first
|
||||
* <Avatar /> ← Child hydrates after parent renders
|
||||
* </User_Card>
|
||||
*
|
||||
* We skip nested .Component_Init elements on first pass (they're inside a parent
|
||||
* that hasn't rendered yet). After each component renders, we recursively scan
|
||||
* its content for children to hydrate.
|
||||
*
|
||||
* == ASYNC COMPLETION ==
|
||||
*
|
||||
* Hydration is async because components may have on_load() methods that fetch data.
|
||||
* We track all component promises and trigger 'jqhtml_ready' only when every
|
||||
* component (including nested ones) has completed its full lifecycle.
|
||||
*
|
||||
* @param {jQuery} [$scope] Scope to search (defaults to body). Recursive calls pass component.$
|
||||
* @returns {Array<Promise>|undefined} Promises for recursive calls; undefined for top-level
|
||||
* jqhtml.boot() handles:
|
||||
* - Finding ._Component_Init elements
|
||||
* - Parsing data-component-init-name / data-component-args
|
||||
* - Calling $element.component(name, args)
|
||||
* - Recursive nested component handling
|
||||
* - Promise tracking for async components
|
||||
*/
|
||||
static _on_framework_modules_init($scope) {
|
||||
const is_top_level = !$scope;
|
||||
const promises = [];
|
||||
const components_needing_init = ($scope || $('body')).find('.Component_Init');
|
||||
|
||||
if (components_needing_init.length > 0) {
|
||||
console_debug('JQHTML_INIT', `Initializing ${components_needing_init.length} DOM components`);
|
||||
}
|
||||
|
||||
components_needing_init.each(function () {
|
||||
const $element = $(this);
|
||||
|
||||
// Guard: Element may have been removed by a parent component's render
|
||||
if (!document.contains($element[0])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Guard: Skip nested components - they'll be processed after their parent renders
|
||||
let parent = $element[0].parentElement;
|
||||
while (parent) {
|
||||
if (parent.classList.contains('Component_Init')) {
|
||||
return;
|
||||
}
|
||||
parent = parent.parentElement;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// STEP 1: Extract hydration data from placeholder element
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
const component_name = $element.attr('data-component-init-name');
|
||||
const args_string = $element.attr('data-component-args');
|
||||
|
||||
let component_args = {};
|
||||
if (args_string) {
|
||||
try {
|
||||
component_args = JSON.parse(args_string);
|
||||
} catch (e) {
|
||||
console.error(`[JQHTML Integration] Failed to parse component args for ${component_name}:`, e);
|
||||
component_args = {};
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// STEP 2: Remove hydration markers (cleanup)
|
||||
//
|
||||
// These attributes served their purpose. Removing them:
|
||||
// - Prevents accidental re-hydration
|
||||
// - Hides implementation details from DOM inspection
|
||||
// - Prevents other code from depending on this internal mechanism
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
$element.removeAttr('data-component-init-name');
|
||||
$element.removeAttr('data-component-args');
|
||||
$element.removeData('component-init-name');
|
||||
$element.removeData('component-args');
|
||||
|
||||
if (component_name) {
|
||||
let component_args_filtered = {};
|
||||
for (const [key, value] of Object.entries(component_args)) {
|
||||
if (key.startsWith('data-')) {
|
||||
component_args_filtered[key.substring(5)] = value;
|
||||
} else {
|
||||
component_args_filtered[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// ─────────────────────────────────────────────────────────
|
||||
// STEP 3: Capture innerHTML for slot/content() processing
|
||||
//
|
||||
// Blade content between <Component>...</Component> tags
|
||||
// becomes available to the template via content() function
|
||||
// ─────────────────────────────────────────────────────────
|
||||
component_args_filtered._inner_html = $element.html();
|
||||
$element.empty();
|
||||
|
||||
// ─────────────────────────────────────────────────────────
|
||||
// STEP 4: Instantiate the component
|
||||
//
|
||||
// Remove .Component_Init first to prevent re-initialization
|
||||
// if this element is somehow scanned again
|
||||
// ─────────────────────────────────────────────────────────
|
||||
$element.removeClass('Component_Init');
|
||||
|
||||
const component_promise = new Promise((resolve) => {
|
||||
// $().component(name, args) creates the component instance,
|
||||
// binds it to the element, and starts the lifecycle
|
||||
let component = $element.component(component_name, component_args_filtered).component();
|
||||
|
||||
// ─────────────────────────────────────────────────────
|
||||
// STEP 5: Recurse after render
|
||||
//
|
||||
// Component's template may contain nested .Component_Init
|
||||
// elements. We must wait for render before we can find them.
|
||||
//
|
||||
// Note: If the template changed the tag (e.g., tag="button"),
|
||||
// the original $element may have been replaced. Always use
|
||||
// component.$ to get the current element.
|
||||
// ─────────────────────────────────────────────────────
|
||||
component.on('render', function () {
|
||||
const nested_promises = Jqhtml_Integration._on_framework_modules_init(component.$);
|
||||
promises.push(...nested_promises);
|
||||
resolve();
|
||||
}).$;
|
||||
});
|
||||
|
||||
promises.push(component_promise);
|
||||
} catch (error) {
|
||||
console.error(`[JQHTML Integration] Failed to initialize component ${component_name}:`, error);
|
||||
console.error('Error details:', error.stack || error);
|
||||
}
|
||||
}
|
||||
static _on_framework_modules_init() {
|
||||
jqhtml.boot().then(() => {
|
||||
Rsx.trigger('jqhtml_ready');
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// COMPLETION: Top-level call waits for all components, then signals ready
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
if (is_top_level) {
|
||||
(async () => {
|
||||
await Promise.all(promises);
|
||||
Rsx.trigger('jqhtml_ready');
|
||||
})();
|
||||
return;
|
||||
}
|
||||
|
||||
// Recursive calls return promises for parent to collect
|
||||
return promises;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"comments": {
|
||||
"blockComment": ["{{--", "--}}"]
|
||||
},
|
||||
"brackets": [
|
||||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"]
|
||||
],
|
||||
"autoClosingPairs": [
|
||||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"],
|
||||
["\"", "\""],
|
||||
["'", "'"],
|
||||
["{{", "}}"],
|
||||
["{!!", "!!}"],
|
||||
["{{--", "--}}"]
|
||||
],
|
||||
"surroundingPairs": [
|
||||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"],
|
||||
["\"", "\""],
|
||||
["'", "'"]
|
||||
]
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.init_blade_language_config = void 0;
|
||||
const vscode = __importStar(require("vscode"));
|
||||
const init_blade_language_config = () => {
|
||||
// HTML empty elements that don't require closing tags
|
||||
const EMPTY_ELEMENTS = [
|
||||
'area',
|
||||
'base',
|
||||
'br',
|
||||
'col',
|
||||
'embed',
|
||||
'hr',
|
||||
'img',
|
||||
'input',
|
||||
'keygen',
|
||||
'link',
|
||||
'menuitem',
|
||||
'meta',
|
||||
'param',
|
||||
'source',
|
||||
'track',
|
||||
'wbr',
|
||||
];
|
||||
// Configure Blade language indentation and auto-closing behavior
|
||||
vscode.languages.setLanguageConfiguration('blade', {
|
||||
indentationRules: {
|
||||
increaseIndentPattern: /<(?!\?|(?:area|base|br|col|frame|hr|html|img|input|link|meta|param)\b|[^>]*\/>)([-_\.A-Za-z0-9]+)(?=\s|>)\b[^>]*>(?!.*<\/\1>)|<!--(?!.*-->)|\{[^}"']*$/,
|
||||
decreaseIndentPattern: /^\s*(<\/(?!html)[-_\.A-Za-z0-9]+\b[^>]*>|-->|\})/,
|
||||
},
|
||||
wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\$\^\&\*\(\)\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\s]+)/g,
|
||||
onEnterRules: [
|
||||
{
|
||||
// When pressing Enter between opening and closing tags, auto-indent
|
||||
beforeText: new RegExp(`<(?!(?:${EMPTY_ELEMENTS.join('|')}))([_:\\w][_:\\w-.\\d]*)([^/>]*(?!/)>)[^<]*$`, 'i'),
|
||||
afterText: /^<\/([_:\w][_:\w-.\d]*)\s*>$/i,
|
||||
action: { indentAction: vscode.IndentAction.IndentOutdent },
|
||||
},
|
||||
{
|
||||
// When pressing Enter after opening tag, auto-indent
|
||||
beforeText: new RegExp(`<(?!(?:${EMPTY_ELEMENTS.join('|')}))(\\w[\\w\\d]*)([^/>]*(?!/)>)[^<]*$`, 'i'),
|
||||
action: { indentAction: vscode.IndentAction.Indent },
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
exports.init_blade_language_config = init_blade_language_config;
|
||||
//# sourceMappingURL=blade_client.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"blade_client.js","sourceRoot":"","sources":["../src/blade_client.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+CAAiC;AAE1B,MAAM,0BAA0B,GAAG,GAAG,EAAE;IAC3C,sDAAsD;IACtD,MAAM,cAAc,GAAa;QAC7B,MAAM;QACN,MAAM;QACN,IAAI;QACJ,KAAK;QACL,OAAO;QACP,IAAI;QACJ,KAAK;QACL,OAAO;QACP,QAAQ;QACR,MAAM;QACN,UAAU;QACV,MAAM;QACN,OAAO;QACP,QAAQ;QACR,OAAO;QACP,KAAK;KACR,CAAC;IAEF,iEAAiE;IACjE,MAAM,CAAC,SAAS,CAAC,wBAAwB,CAAC,OAAO,EAAE;QAC/C,gBAAgB,EAAE;YACd,qBAAqB,EACjB,wJAAwJ;YAC5J,qBAAqB,EACjB,kDAAkD;SACzD;QACD,WAAW,EACP,gFAAgF;QACpF,YAAY,EAAE;YACV;gBACI,oEAAoE;gBACpE,UAAU,EAAE,IAAI,MAAM,CAClB,UAAU,cAAc,CAAC,IAAI,CACzB,GAAG,CACN,8CAA8C,EAC/C,GAAG,CACN;gBACD,SAAS,EAAE,+BAA+B;gBAC1C,MAAM,EAAE,EAAE,YAAY,EAAE,MAAM,CAAC,YAAY,CAAC,aAAa,EAAE;aAC9D;YACD;gBACI,qDAAqD;gBACrD,UAAU,EAAE,IAAI,MAAM,CAClB,UAAU,cAAc,CAAC,IAAI,CACzB,GAAG,CACN,sCAAsC,EACvC,GAAG,CACN;gBACD,MAAM,EAAE,EAAE,YAAY,EAAE,MAAM,CAAC,YAAY,CAAC,MAAM,EAAE;aACvD;SACJ;KACJ,CAAC,CAAC;AACP,CAAC,CAAC;AAvDW,QAAA,0BAA0B,8BAuDrC"}
|
||||
@@ -1,81 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.BladeComponentSemanticTokensProvider = void 0;
|
||||
const vscode = __importStar(require("vscode"));
|
||||
/**
|
||||
* Provides semantic tokens for uppercase component tags in Blade files
|
||||
* Highlights component tag names in light green
|
||||
* Highlights tag="" attribute in orange on jqhtml components
|
||||
*/
|
||||
class BladeComponentSemanticTokensProvider {
|
||||
async provideDocumentSemanticTokens(document) {
|
||||
const tokens_builder = new vscode.SemanticTokensBuilder();
|
||||
if (document.languageId !== 'blade') {
|
||||
return tokens_builder.build();
|
||||
}
|
||||
const text = document.getText();
|
||||
// Match opening tags that start with uppercase letter to find jqhtml components
|
||||
// Matches: <ComponentName ...>, captures the entire tag up to >
|
||||
const component_tag_regex = /<([A-Z][a-zA-Z0-9_]*)([^>]*?)>/g;
|
||||
let component_match;
|
||||
while ((component_match = component_tag_regex.exec(text)) !== null) {
|
||||
const tag_name = component_match[1];
|
||||
const tag_attributes = component_match[2];
|
||||
const tag_start = component_match.index + component_match[0].indexOf(tag_name);
|
||||
const tag_position = document.positionAt(tag_start);
|
||||
// Push token for the component tag name
|
||||
// Token type 0 maps to 'class' which VS Code themes style as entity.name.class (turquoise/cyan)
|
||||
tokens_builder.push(tag_position.line, tag_position.character, tag_name.length, 0, 0);
|
||||
// Now look for tag="" attribute within this component's attributes
|
||||
// Matches: tag="..." or tag='...'
|
||||
const tag_attr_regex = /\btag\s*=/g;
|
||||
let attr_match;
|
||||
while ((attr_match = tag_attr_regex.exec(tag_attributes)) !== null) {
|
||||
// Calculate the position of 'tag' within the document
|
||||
const attr_start = component_match.index + component_match[0].indexOf(tag_attributes) + attr_match.index;
|
||||
const attr_position = document.positionAt(attr_start);
|
||||
// Push token for 'tag' attribute name
|
||||
// Token type 1 maps to 'jqhtmlTagAttribute' which we'll define to be orange
|
||||
tokens_builder.push(attr_position.line, attr_position.character, 3, 1, 0);
|
||||
}
|
||||
}
|
||||
// Also match closing tags that start with uppercase letter
|
||||
// Matches: </ComponentName>
|
||||
const closing_tag_regex = /<\/([A-Z][a-zA-Z0-9_]*)/g;
|
||||
let closing_match;
|
||||
while ((closing_match = closing_tag_regex.exec(text)) !== null) {
|
||||
const tag_name = closing_match[1];
|
||||
const tag_start = closing_match.index + closing_match[0].indexOf(tag_name);
|
||||
const position = document.positionAt(tag_start);
|
||||
// Push token for the tag name
|
||||
// Token type 0 maps to 'class' which VS Code themes style as entity.name.class (turquoise/cyan)
|
||||
tokens_builder.push(position.line, position.character, tag_name.length, 0, 0);
|
||||
}
|
||||
return tokens_builder.build();
|
||||
}
|
||||
}
|
||||
exports.BladeComponentSemanticTokensProvider = BladeComponentSemanticTokensProvider;
|
||||
//# sourceMappingURL=blade_component_provider.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"blade_component_provider.js","sourceRoot":"","sources":["../src/blade_component_provider.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+CAAiC;AAEjC;;;;GAIG;AACH,MAAa,oCAAoC;IAC7C,KAAK,CAAC,6BAA6B,CAAC,QAA6B;QAC7D,MAAM,cAAc,GAAG,IAAI,MAAM,CAAC,qBAAqB,EAAE,CAAC;QAE1D,IAAI,QAAQ,CAAC,UAAU,KAAK,OAAO,EAAE;YACjC,OAAO,cAAc,CAAC,KAAK,EAAE,CAAC;SACjC;QAED,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,EAAE,CAAC;QAEhC,gFAAgF;QAChF,gEAAgE;QAChE,MAAM,mBAAmB,GAAG,iCAAiC,CAAC;QAC9D,IAAI,eAAe,CAAC;QAEpB,OAAO,CAAC,eAAe,GAAG,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE;YAChE,MAAM,QAAQ,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC;YACpC,MAAM,cAAc,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC;YAC1C,MAAM,SAAS,GAAG,eAAe,CAAC,KAAK,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;YAC/E,MAAM,YAAY,GAAG,QAAQ,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;YAEpD,wCAAwC;YACxC,gGAAgG;YAChG,cAAc,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,YAAY,CAAC,SAAS,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAEtF,mEAAmE;YACnE,kCAAkC;YAClC,MAAM,cAAc,GAAG,YAAY,CAAC;YACpC,IAAI,UAAU,CAAC;YAEf,OAAO,CAAC,UAAU,GAAG,cAAc,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,KAAK,IAAI,EAAE;gBAChE,sDAAsD;gBACtD,MAAM,UAAU,GAAG,eAAe,CAAC,KAAK,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC;gBACzG,MAAM,aAAa,GAAG,QAAQ,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;gBAEtD,sCAAsC;gBACtC,4EAA4E;gBAC5E,cAAc,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,aAAa,CAAC,SAAS,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;aAC7E;SACJ;QAED,2DAA2D;QAC3D,4BAA4B;QAC5B,MAAM,iBAAiB,GAAG,0BAA0B,CAAC;QACrD,IAAI,aAAa,CAAC;QAElB,OAAO,CAAC,aAAa,GAAG,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE;YAC5D,MAAM,QAAQ,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;YAClC,MAAM,SAAS,GAAG,aAAa,CAAC,KAAK,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;YAC3E,MAAM,QAAQ,GAAG,QAAQ,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;YAEhD,8BAA8B;YAC9B,gGAAgG;YAChG,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;SACjF;QAED,OAAO,cAAc,CAAC,KAAK,EAAE,CAAC;IAClC,CAAC;CACJ;AA1DD,oFA0DC"}
|
||||
@@ -1,114 +0,0 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.blade_spacer = void 0;
|
||||
const vscode = __importStar(require("vscode"));
|
||||
const config_1 = require("./config");
|
||||
const TAG_DOUBLE = 0;
|
||||
const TAG_UNESCAPED = 1;
|
||||
const TAG_COMMENT = 2;
|
||||
const snippets = {
|
||||
[TAG_DOUBLE]: '{{ ${1:${TM_SELECTED_TEXT/[{}]//g}} }}$0',
|
||||
[TAG_UNESCAPED]: '{!! ${1:${TM_SELECTED_TEXT/[{} !]//g}} !!}$0',
|
||||
[TAG_COMMENT]: '{{-- ${1:${TM_SELECTED_TEXT/(--)|[{} ]//g}} --}}$0',
|
||||
};
|
||||
const triggers = ['{}', '!', '-', '{'];
|
||||
const regexes = [
|
||||
/({{(?!\s|-))(.*?)(}})/,
|
||||
/({!!(?!\s))(.*?)?(}?)/,
|
||||
/({{[\s]?--)(.*?)?(}})/,
|
||||
];
|
||||
const translate = (position, offset) => {
|
||||
try {
|
||||
return position.translate(0, offset);
|
||||
}
|
||||
catch (error) {
|
||||
// VS Code doesn't like negative numbers passed
|
||||
// to translate (even though it works fine), so
|
||||
// this block prevents debug console errors
|
||||
}
|
||||
return position;
|
||||
};
|
||||
const chars_for_change = (doc, change) => {
|
||||
if (change.text === '!') {
|
||||
return 2;
|
||||
}
|
||||
if (change.text !== '-') {
|
||||
return 1;
|
||||
}
|
||||
const start = translate(change.range.start, -2);
|
||||
const end = translate(change.range.start, -1);
|
||||
return doc.getText(new vscode.Range(start, end)) === ' ' ? 4 : 3;
|
||||
};
|
||||
const blade_spacer = async (e, editor) => {
|
||||
const config = (0, config_1.get_config)();
|
||||
if (!config.get('enableBladeAutoSpacing', true) ||
|
||||
!editor ||
|
||||
editor.document.fileName.indexOf('.blade.php') === -1) {
|
||||
return;
|
||||
}
|
||||
let tag_type = -1;
|
||||
let ranges = [];
|
||||
let offsets = [];
|
||||
// Changes (per line) come in right-to-left when we need them left-to-right
|
||||
const changes = e.contentChanges.slice().reverse();
|
||||
changes.forEach((change) => {
|
||||
if (triggers.indexOf(change.text) === -1) {
|
||||
return;
|
||||
}
|
||||
if (!offsets[change.range.start.line]) {
|
||||
offsets[change.range.start.line] = 0;
|
||||
}
|
||||
const start_offset = offsets[change.range.start.line] -
|
||||
chars_for_change(e.document, change);
|
||||
const start = translate(change.range.start, start_offset);
|
||||
const line_end = e.document.lineAt(start.line).range.end;
|
||||
for (let i = 0; i < regexes.length; i++) {
|
||||
// If we typed a - or a !, don't consider the "double" tag type
|
||||
if (i === TAG_DOUBLE && ['-', '!'].indexOf(change.text) !== -1) {
|
||||
continue;
|
||||
}
|
||||
// Only look at unescaped tags if we need to
|
||||
if (i === TAG_UNESCAPED && change.text !== '!') {
|
||||
continue;
|
||||
}
|
||||
// Only look at comment tags if we need to
|
||||
if (i === TAG_COMMENT && change.text !== '-') {
|
||||
continue;
|
||||
}
|
||||
const tag = regexes[i].exec(e.document.getText(new vscode.Range(start, line_end)));
|
||||
if (tag) {
|
||||
tag_type = i;
|
||||
ranges.push(new vscode.Range(start, start.translate(0, tag[0].length)));
|
||||
offsets[start.line] += tag[1].length;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (ranges.length > 0 && snippets[tag_type]) {
|
||||
editor.insertSnippet(new vscode.SnippetString(snippets[tag_type]), ranges);
|
||||
}
|
||||
};
|
||||
exports.blade_spacer = blade_spacer;
|
||||
//# sourceMappingURL=blade_spacer.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"blade_spacer.js","sourceRoot":"","sources":["../src/blade_spacer.ts"],"names":[],"mappings":"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"}
|
||||
@@ -44,11 +44,6 @@
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "Set to 'rspade' to enable RSpade framework features"
|
||||
},
|
||||
"rspade.enableBladeAutoSpacing": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Automatically add spaces inside Blade tags when typing"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -109,26 +104,19 @@
|
||||
"id": "conventionMethod",
|
||||
"superType": "method",
|
||||
"description": "Convention method automatically called by RSX framework"
|
||||
},
|
||||
{
|
||||
"id": "jqhtmlTagAttribute",
|
||||
"superType": "parameter",
|
||||
"description": "The tag attribute on jqhtml components"
|
||||
}
|
||||
],
|
||||
"semanticTokenScopes": [
|
||||
{
|
||||
"scopes": {
|
||||
"conventionMethod": ["entity.name.function.convention.rspade"],
|
||||
"jqhtmlTagAttribute": ["entity.other.attribute-name.jqhtml.tag"]
|
||||
"conventionMethod": ["entity.name.function.convention.rspade"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"configurationDefaults": {
|
||||
"editor.semanticTokenColorCustomizations": {
|
||||
"rules": {
|
||||
"conventionMethod": "#FFA500",
|
||||
"jqhtmlTagAttribute": "#FFA500"
|
||||
"conventionMethod": "#FFA500"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export const init_blade_language_config = () => {
|
||||
// HTML empty elements that don't require closing tags
|
||||
const EMPTY_ELEMENTS: string[] = [
|
||||
'area',
|
||||
'base',
|
||||
'br',
|
||||
'col',
|
||||
'embed',
|
||||
'hr',
|
||||
'img',
|
||||
'input',
|
||||
'keygen',
|
||||
'link',
|
||||
'menuitem',
|
||||
'meta',
|
||||
'param',
|
||||
'source',
|
||||
'track',
|
||||
'wbr',
|
||||
];
|
||||
|
||||
// Configure Blade language indentation and auto-closing behavior
|
||||
vscode.languages.setLanguageConfiguration('blade', {
|
||||
indentationRules: {
|
||||
increaseIndentPattern:
|
||||
/<(?!\?|(?:area|base|br|col|frame|hr|html|img|input|link|meta|param)\b|[^>]*\/>)([-_\.A-Za-z0-9]+)(?=\s|>)\b[^>]*>(?!.*<\/\1>)|<!--(?!.*-->)|\{[^}"']*$/,
|
||||
decreaseIndentPattern:
|
||||
/^\s*(<\/(?!html)[-_\.A-Za-z0-9]+\b[^>]*>|-->|\})/,
|
||||
},
|
||||
wordPattern:
|
||||
/(-?\d*\.\d\w*)|([^\`\~\!\@\$\^\&\*\(\)\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\s]+)/g,
|
||||
onEnterRules: [
|
||||
{
|
||||
// When pressing Enter between opening and closing tags, auto-indent
|
||||
beforeText: new RegExp(
|
||||
`<(?!(?:${EMPTY_ELEMENTS.join(
|
||||
'|'
|
||||
)}))([_:\\w][_:\\w-.\\d]*)([^/>]*(?!/)>)[^<]*$`,
|
||||
'i'
|
||||
),
|
||||
afterText: /^<\/([_:\w][_:\w-.\d]*)\s*>$/i,
|
||||
action: { indentAction: vscode.IndentAction.IndentOutdent },
|
||||
},
|
||||
{
|
||||
// When pressing Enter after opening tag, auto-indent
|
||||
beforeText: new RegExp(
|
||||
`<(?!(?:${EMPTY_ELEMENTS.join(
|
||||
'|'
|
||||
)}))(\\w[\\w\\d]*)([^/>]*(?!/)>)[^<]*$`,
|
||||
'i'
|
||||
),
|
||||
action: { indentAction: vscode.IndentAction.Indent },
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
@@ -1,66 +0,0 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
/**
|
||||
* Provides semantic tokens for uppercase component tags in Blade files
|
||||
* Highlights component tag names in light green
|
||||
* Highlights tag="" attribute in orange on jqhtml components
|
||||
*/
|
||||
export class BladeComponentSemanticTokensProvider implements vscode.DocumentSemanticTokensProvider {
|
||||
async provideDocumentSemanticTokens(document: vscode.TextDocument): Promise<vscode.SemanticTokens> {
|
||||
const tokens_builder = new vscode.SemanticTokensBuilder();
|
||||
|
||||
if (document.languageId !== 'blade') {
|
||||
return tokens_builder.build();
|
||||
}
|
||||
|
||||
const text = document.getText();
|
||||
|
||||
// Match opening tags that start with uppercase letter to find jqhtml components
|
||||
// Matches: <ComponentName ...>, captures the entire tag up to >
|
||||
const component_tag_regex = /<([A-Z][a-zA-Z0-9_]*)([^>]*?)>/g;
|
||||
let component_match;
|
||||
|
||||
while ((component_match = component_tag_regex.exec(text)) !== null) {
|
||||
const tag_name = component_match[1];
|
||||
const tag_attributes = component_match[2];
|
||||
const tag_start = component_match.index + component_match[0].indexOf(tag_name);
|
||||
const tag_position = document.positionAt(tag_start);
|
||||
|
||||
// Push token for the component tag name
|
||||
// Token type 0 maps to 'class' which VS Code themes style as entity.name.class (turquoise/cyan)
|
||||
tokens_builder.push(tag_position.line, tag_position.character, tag_name.length, 0, 0);
|
||||
|
||||
// Now look for tag="" attribute within this component's attributes
|
||||
// Matches: tag="..." or tag='...'
|
||||
const tag_attr_regex = /\btag\s*=/g;
|
||||
let attr_match;
|
||||
|
||||
while ((attr_match = tag_attr_regex.exec(tag_attributes)) !== null) {
|
||||
// Calculate the position of 'tag' within the document
|
||||
const attr_start = component_match.index + component_match[0].indexOf(tag_attributes) + attr_match.index;
|
||||
const attr_position = document.positionAt(attr_start);
|
||||
|
||||
// Push token for 'tag' attribute name
|
||||
// Token type 1 maps to 'jqhtmlTagAttribute' which we'll define to be orange
|
||||
tokens_builder.push(attr_position.line, attr_position.character, 3, 1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Also match closing tags that start with uppercase letter
|
||||
// Matches: </ComponentName>
|
||||
const closing_tag_regex = /<\/([A-Z][a-zA-Z0-9_]*)/g;
|
||||
let closing_match;
|
||||
|
||||
while ((closing_match = closing_tag_regex.exec(text)) !== null) {
|
||||
const tag_name = closing_match[1];
|
||||
const tag_start = closing_match.index + closing_match[0].indexOf(tag_name);
|
||||
const position = document.positionAt(tag_start);
|
||||
|
||||
// Push token for the tag name
|
||||
// Token type 0 maps to 'class' which VS Code themes style as entity.name.class (turquoise/cyan)
|
||||
tokens_builder.push(position.line, position.character, tag_name.length, 0, 0);
|
||||
}
|
||||
|
||||
return tokens_builder.build();
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { get_config } from './config';
|
||||
|
||||
const TAG_DOUBLE = 0;
|
||||
const TAG_UNESCAPED = 1;
|
||||
const TAG_COMMENT = 2;
|
||||
|
||||
const snippets: Record<number, string> = {
|
||||
[TAG_DOUBLE]: '{{ ${1:${TM_SELECTED_TEXT/[{}]//g}} }}$0',
|
||||
[TAG_UNESCAPED]: '{!! ${1:${TM_SELECTED_TEXT/[{} !]//g}} !!}$0',
|
||||
[TAG_COMMENT]: '{{-- ${1:${TM_SELECTED_TEXT/(--)|[{} ]//g}} --}}$0',
|
||||
};
|
||||
|
||||
const triggers = ['{}', '!', '-', '{'];
|
||||
|
||||
const regexes = [
|
||||
/({{(?!\s|-))(.*?)(}})/,
|
||||
/({!!(?!\s))(.*?)?(}?)/,
|
||||
/({{[\s]?--)(.*?)?(}})/,
|
||||
];
|
||||
|
||||
const translate = (position: vscode.Position, offset: number): vscode.Position => {
|
||||
try {
|
||||
return position.translate(0, offset);
|
||||
} catch (error) {
|
||||
// VS Code doesn't like negative numbers passed
|
||||
// to translate (even though it works fine), so
|
||||
// this block prevents debug console errors
|
||||
}
|
||||
|
||||
return position;
|
||||
};
|
||||
|
||||
const chars_for_change = (
|
||||
doc: vscode.TextDocument,
|
||||
change: vscode.TextDocumentContentChangeEvent
|
||||
): number => {
|
||||
if (change.text === '!') {
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (change.text !== '-') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const start = translate(change.range.start, -2);
|
||||
const end = translate(change.range.start, -1);
|
||||
|
||||
return doc.getText(new vscode.Range(start, end)) === ' ' ? 4 : 3;
|
||||
};
|
||||
|
||||
export const blade_spacer = async (
|
||||
e: vscode.TextDocumentChangeEvent,
|
||||
editor?: vscode.TextEditor
|
||||
) => {
|
||||
const config = get_config();
|
||||
|
||||
if (
|
||||
!config.get('enableBladeAutoSpacing', true) ||
|
||||
!editor ||
|
||||
editor.document.fileName.indexOf('.blade.php') === -1
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
let tag_type: number = -1;
|
||||
let ranges: vscode.Range[] = [];
|
||||
let offsets: number[] = [];
|
||||
|
||||
// Changes (per line) come in right-to-left when we need them left-to-right
|
||||
const changes = e.contentChanges.slice().reverse();
|
||||
|
||||
changes.forEach((change) => {
|
||||
if (triggers.indexOf(change.text) === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!offsets[change.range.start.line]) {
|
||||
offsets[change.range.start.line] = 0;
|
||||
}
|
||||
|
||||
const start_offset =
|
||||
offsets[change.range.start.line] -
|
||||
chars_for_change(e.document, change);
|
||||
|
||||
const start = translate(change.range.start, start_offset);
|
||||
const line_end = e.document.lineAt(start.line).range.end;
|
||||
|
||||
for (let i = 0; i < regexes.length; i++) {
|
||||
// If we typed a - or a !, don't consider the "double" tag type
|
||||
if (i === TAG_DOUBLE && ['-', '!'].indexOf(change.text) !== -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only look at unescaped tags if we need to
|
||||
if (i === TAG_UNESCAPED && change.text !== '!') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only look at comment tags if we need to
|
||||
if (i === TAG_COMMENT && change.text !== '-') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const tag = regexes[i].exec(
|
||||
e.document.getText(new vscode.Range(start, line_end))
|
||||
);
|
||||
|
||||
if (tag) {
|
||||
tag_type = i;
|
||||
ranges.push(
|
||||
new vscode.Range(start, start.translate(0, tag[0].length))
|
||||
);
|
||||
offsets[start.line] += tag[1].length;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (ranges.length > 0 && snippets[tag_type]) {
|
||||
editor.insertSnippet(new vscode.SnippetString(snippets[tag_type]), ranges);
|
||||
}
|
||||
};
|
||||
@@ -7,14 +7,11 @@ import { RspadeDefinitionProvider } from './definition_provider';
|
||||
import { DebugClient } from './debug_client';
|
||||
import { get_config } from './config';
|
||||
import { LaravelCompletionProvider } from './laravel_completion_provider';
|
||||
import { blade_spacer } from './blade_spacer';
|
||||
import { init_blade_language_config } from './blade_client';
|
||||
import { ConventionMethodHoverProvider, ConventionMethodDiagnosticProvider, ConventionMethodDefinitionProvider } from './convention_method_provider';
|
||||
import { CommentFileReferenceDefinitionProvider } from './comment_file_reference_provider';
|
||||
import { JqhtmlLifecycleHoverProvider, JqhtmlLifecycleDiagnosticProvider } from './jqhtml_lifecycle_provider';
|
||||
import { CombinedSemanticTokensProvider } from './combined_semantic_provider';
|
||||
import { PhpAttributeSemanticTokensProvider } from './php_attribute_provider';
|
||||
import { BladeComponentSemanticTokensProvider } from './blade_component_provider';
|
||||
import { AutoRenameProvider } from './auto_rename_provider';
|
||||
import { FolderColorProvider } from './folder_color_provider';
|
||||
import { GitStatusProvider } from './git_status_provider';
|
||||
@@ -255,18 +252,6 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
console.log('RSpade formatter registered for PHP files');
|
||||
|
||||
// Initialize Blade language configuration (indentation, auto-closing)
|
||||
init_blade_language_config();
|
||||
console.log('Blade language configuration initialized');
|
||||
|
||||
// Register Blade auto-spacing on text change
|
||||
context.subscriptions.push(
|
||||
vscode.workspace.onDidChangeTextDocument((event) => {
|
||||
blade_spacer(event, vscode.window.activeTextEditor);
|
||||
})
|
||||
);
|
||||
console.log('Blade auto-spacing enabled');
|
||||
|
||||
// Register definition provider for JavaScript/TypeScript and PHP/Blade/jqhtml files
|
||||
context.subscriptions.push(
|
||||
vscode.languages.registerDefinitionProvider(
|
||||
@@ -372,19 +357,6 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
console.log('PHP attribute provider registered for PHP files');
|
||||
|
||||
// Register Blade component provider for uppercase component tags
|
||||
const blade_component_provider = new BladeComponentSemanticTokensProvider();
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.languages.registerDocumentSemanticTokensProvider(
|
||||
[{ language: 'blade' }, { pattern: '**/*.blade.php' }],
|
||||
blade_component_provider,
|
||||
new vscode.SemanticTokensLegend(['class', 'jqhtmlTagAttribute'])
|
||||
)
|
||||
);
|
||||
|
||||
console.log('Blade component provider registered for Blade files');
|
||||
|
||||
// Debug client disabled
|
||||
// debug_client = new DebugClient(formatting_provider as any);
|
||||
// debug_client.start().catch(error => {
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"name": "Blade JQHTML Components",
|
||||
"scopeName": "source.jqhtml.injection",
|
||||
"injectionSelector": "L:text.blade, L:text.html.php",
|
||||
"patterns": [
|
||||
{
|
||||
"comment": "JQHTML Component Tags",
|
||||
"name": "meta.tag.jqhtml.component",
|
||||
"match": "(</?)(([A-Z][A-Za-z0-9_]*))(?=[\\s/>])",
|
||||
"captures": {
|
||||
"1": {
|
||||
"name": "punctuation.definition.tag.html"
|
||||
},
|
||||
"2": {
|
||||
"name": "entity.name.tag.component.jqhtml"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user