// The module 'vscode' contains the VS Code extensibility API // Import the module and reference it with the alias vscode in your code below import * as vscode from 'vscode'; import * as fs from 'fs'; import * as path from 'path'; import * as Handlebars from 'handlebars'; async function getTemplatesDir(workspaceFolders: readonly vscode.WorkspaceFolder[] | undefined): Promise { if (!workspaceFolders) return undefined; for (const folder of workspaceFolders) { const candidate = path.join(folder.uri.fsPath, 'templates'); if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) { return candidate; } } return undefined; } async function pickTemplate(templatesDir: string): Promise { const templates = fs.readdirSync(templatesDir).filter(f => fs.statSync(path.join(templatesDir, f)).isDirectory()); if (templates.length === 0) { vscode.window.showWarningMessage('В папке templates нет шаблонов.'); return undefined; } return vscode.window.showQuickPick(templates, { placeHolder: 'Выберите шаблон' }); } function readDirRecursive(src: string): string[] { let results: string[] = []; const list = fs.readdirSync(src); list.forEach(function(file) { const filePath = path.join(src, file); const stat = fs.statSync(filePath); if (stat && stat.isDirectory()) { results = results.concat(readDirRecursive(filePath)); } else { results.push(filePath); } }); return results; } function toUpperCaseFirst(str: string): string { return str.charAt(0).toUpperCase() + str.slice(1); } function toPascalCase(str: string): string { return str .replace(/[-_ ]+/g, ' ') .split(' ') .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) .join(''); } function toCamelCase(str: string): string { const pascal = toPascalCase(str); return pascal.charAt(0).toLowerCase() + pascal.slice(1); } function toSnakeCase(str: string): string { return str .replace(/([a-z])([A-Z])/g, '$1_$2') .replace(/[-\s]+/g, '_') .toLowerCase(); } function toKebabCase(str: string): string { return str .replace(/([a-z])([A-Z])/g, '$1-$2') .replace(/[_\s]+/g, '-') .toLowerCase(); } function toScreamingSnakeCase(str: string): string { return toSnakeCase(str).toUpperCase(); } function toUpperCaseAll(str: string): string { return str.replace(/[-_\s]+/g, '').toUpperCase(); } function toLowerCaseAll(str: string): string { return str.replace(/[-_\s]+/g, '').toLowerCase(); } function copyTemplate(templateDir: string, targetDir: string, name: string) { const vars = { name, // как ввёл пользователь nameUpperCase: toUpperCaseFirst(name), nameLowerCase: name.toLowerCase(), namePascalCase: toPascalCase(name), nameCamelCase: toCamelCase(name), nameSnakeCase: toSnakeCase(name), nameKebabCase: toKebabCase(name), nameScreamingSnakeCase: toScreamingSnakeCase(name), nameUpperCaseAll: toUpperCaseAll(name), nameLowerCaseAll: toLowerCaseAll(name) }; const files = readDirRecursive(templateDir); for (const file of files) { const relPath = path.relative(templateDir, file); const relPathTmpl = Handlebars.compile(relPath); const targetRelPath = relPathTmpl(vars); const targetPath = path.join(targetDir, targetRelPath); const content = fs.readFileSync(file, 'utf8'); const contentTmpl = Handlebars.compile(content); const rendered = contentTmpl(vars); fs.mkdirSync(path.dirname(targetPath), { recursive: true }); fs.writeFileSync(targetPath, rendered, { flag: 'wx' }); } } // Новый способ поиска переменных в шаблоне function getAllTemplateVariables(templateDir: string): Set { const files = readDirRecursive(templateDir); // Ищем {{ variable }} и {{ variable.suffix }} const varRegex = /{{\s*([\w]+)(?:\.[\w]+)?\s*}}/g; const vars = new Set(); for (const file of files) { // Пути let relPath = path.relative(templateDir, file); let match; while ((match = varRegex.exec(relPath)) !== null) { vars.add(match[1]); // только базовое имя } // Содержимое const content = fs.readFileSync(file, 'utf8'); while ((match = varRegex.exec(content)) !== null) { vars.add(match[1]); } } return vars; } // Мапа модификаторов и функций const CASE_MODIFIERS: Record string> = { 'pascalCase': toPascalCase, 'camelCase': toCamelCase, 'snakeCase': toSnakeCase, 'kebabCase': toKebabCase, 'screamingSnakeCase': toScreamingSnakeCase, 'upperCase': toUpperCaseFirst, 'lowerCase': (s: string) => s.toLowerCase(), 'upperCaseAll': toUpperCaseAll, 'lowerCaseAll': toLowerCaseAll, }; // Генерируем объект переменных для шаблона function buildVarsObject(userVars: Record): Record { const result: Record = {}; for (const [base, value] of Object.entries(userVars)) { result[base] = value; for (const [mod, fn] of Object.entries(CASE_MODIFIERS)) { result[`${base}.${mod}`] = fn(value); } } return result; } async function collectUserVars(baseVars: Set): Promise> { const result: Record = {}; for (const v of baseVars) { const input = await vscode.window.showInputBox({ prompt: `Введите значение для ${v}`, placeHolder: `{{${v}}}` }); if (!input) throw new Error(`Значение для ${v} не введено`); result[v] = input; } return result; } // --- Собственная обработка шаблонов --- function applyTemplate(str: string, vars: Record, modifiers: Record string>): string { return str.replace(/{{\s*([a-zA-Z0-9_]+)(?:\.([a-zA-Z0-9_]+))?\s*}}/g, (_, varName, mod) => { let value = vars[varName]; if (value === undefined) return ''; if (mod && modifiers[mod]) { return modifiers[mod](value); } return value; }); } function copyTemplateWithVars(templateDir: string, targetDir: string, vars: Record, overwriteFiles: boolean = false, dict?: Record, templateName?: string): boolean { const files = readDirRecursive(templateDir); // Собираем все папки, которые будут созданы на первом уровне const firstLevelDirs = new Set(); for (const file of files) { const relPath = path.relative(templateDir, file); const targetRelPath = applyTemplate(relPath, vars, CASE_MODIFIERS); const firstLevel = targetRelPath.split(path.sep)[0]; firstLevelDirs.add(firstLevel); } // Проверяем существование этих папок/файлов if (!overwriteFiles && dict) { for (const dir of firstLevelDirs) { const checkPath = path.join(targetDir, dir); if (fs.existsSync(checkPath)) { vscode.window.showErrorMessage(`${dict.fileExistsNoOverwrite}: ${checkPath}`); return false; } } } let createdCount = 0; for (const file of files) { const relPath = path.relative(templateDir, file); const targetRelPath = applyTemplate(relPath, vars, CASE_MODIFIERS); const targetPath = path.join(targetDir, targetRelPath); try { if (!overwriteFiles && fs.existsSync(targetPath) && dict) { const errMsg = `${dict.fileExistsNoOverwrite}: ${targetPath}`; vscode.window.showErrorMessage(errMsg); break; } const content = fs.readFileSync(file, 'utf8'); const rendered = applyTemplate(content, vars, CASE_MODIFIERS); fs.mkdirSync(path.dirname(targetPath), { recursive: true }); fs.writeFileSync(targetPath, rendered, { flag: overwriteFiles ? 'w' : 'wx' }); createdCount++; } catch (err: any) { if (err && err.code === 'EEXIST' && dict) { vscode.window.showErrorMessage(`${dict.fileExists}: ${targetPath}`); } else if (dict) { vscode.window.showErrorMessage(`${dict.createError}: ${err?.message || err}`); } break; } } if (createdCount > 0 && dict && templateName) { vscode.window.showInformationMessage(dict.createSuccess.replace('{{template}}', templateName)); } return createdCount > 0; } const I18N_DICTIONARIES: Record> = { ru: { destinationPath: 'Путь назначения', chooseTemplate: 'Выберите шаблон', enterVariables: 'Введите значения переменных', varInputHint: 'без скобок {{ }}', create: 'Создать', selectTemplate: 'Шаблон', fileExistsNoOverwrite: 'Файл или папка уже существует и перезапись запрещена', fileExists: 'Файл или папка уже существует', createSuccess: 'Структура {{template}} успешно создана.', createError: 'Ошибка при создании структуры', noTemplates: 'В папке шаблонов нет шаблонов.', templatesNotFound: 'Папка шаблонов не найдена:', noFolders: 'Нет открытых папок рабочего пространства.', inputName: 'Введите имя для шаблона', }, en: { destinationPath: 'Destination path', chooseTemplate: 'Choose template', enterVariables: 'Enter variables', varInputHint: 'without curly braces {{ }}', create: 'Create', selectTemplate: 'Template', fileExistsNoOverwrite: 'File or folder already exists and overwrite is disabled', fileExists: 'File or folder already exists', createSuccess: 'Structure {{template}} created successfully.', createError: 'Error creating structure', noTemplates: 'No templates found in templates folder.', templatesNotFound: 'Templates folder not found:', noFolders: 'No workspace folders open.', inputName: 'Enter name for template', } }; const SETTINGS_I18N: Record> = { ru: { title: 'Настройки myTemplateGenerator', templatesPath: 'Путь к шаблонам:', overwriteFiles: 'Перезаписывать существующие файлы', inputMode: 'Способ ввода переменных:', inputModeWebview: 'Webview (форма)', inputModeInputBox: 'InputBox (по одной)', language: 'Язык интерфейса:', save: 'Сохранить' }, en: { title: 'myTemplateGenerator Settings', templatesPath: 'Templates path:', overwriteFiles: 'Overwrite existing files', inputMode: 'Variable input method:', inputModeWebview: 'Webview (form)', inputModeInputBox: 'InputBox (one by one)', language: 'Interface language:', save: 'Save' } }; interface MyTemplateGeneratorConfig { templatesPath: string; overwriteFiles: boolean; inputMode: 'webview' | 'inputBox'; language?: string; } const DEFAULT_CONFIG: MyTemplateGeneratorConfig = { templatesPath: 'templates', overwriteFiles: false, inputMode: 'webview', language: 'en', }; function getConfigPath(): string | undefined { const folders = vscode.workspace.workspaceFolders; if (!folders || folders.length === 0) return undefined; return path.join(folders[0].uri.fsPath, 'mytemplategenerator.json'); } function readConfig(): MyTemplateGeneratorConfig { const configPath = getConfigPath(); if (configPath && fs.existsSync(configPath)) { try { const raw = fs.readFileSync(configPath, 'utf8'); return { ...DEFAULT_CONFIG, ...JSON.parse(raw) }; } catch (e) { vscode.window.showErrorMessage('Ошибка чтения mytemplategenerator.json, используются значения по умолчанию'); } } // Можно добавить чтение из settings.json, если нужно return DEFAULT_CONFIG; } function writeConfig(config: MyTemplateGeneratorConfig) { const configPath = getConfigPath(); if (!configPath) { vscode.window.showErrorMessage('Не удалось определить путь к mytemplategenerator.json'); return; } fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8'); } async function showConfigWebview(context: vscode.ExtensionContext) { const config = readConfig(); let language = config.language || 'ru'; return new Promise((resolve) => { const panel = vscode.window.createWebviewPanel( 'mytemplategeneratorConfig', SETTINGS_I18N[language]?.title || SETTINGS_I18N['ru'].title, vscode.ViewColumn.Active, { enableScripts: true } ); function setHtml() { const dict = SETTINGS_I18N[language] || SETTINGS_I18N['ru']; panel.webview.html = ` ${dict.title}

${dict.title}

`; } setHtml(); panel.webview.onDidReceiveMessage( message => { if (message.type === 'save') { writeConfig(message.data); vscode.window.showInformationMessage('Настройки myTemplateGenerator сохранены!'); panel.dispose(); resolve(); } else if (message.type === 'changeLanguage') { language = message.language; setHtml(); } }, undefined, context.subscriptions ); panel.onDidDispose(() => resolve(), null, context.subscriptions); }); } async function showTemplateAndVarsWebview(context: vscode.ExtensionContext, templatesDir: string, targetPath: string, initialLanguage: string): Promise<{template: string, vars: Record} | undefined> { let language = initialLanguage; function getDict() { return I18N_DICTIONARIES[language] || I18N_DICTIONARIES['ru']; } const templates = fs.readdirSync(templatesDir).filter(f => fs.statSync(path.join(templatesDir, f)).isDirectory()); return new Promise((resolve) => { const panel = vscode.window.createWebviewPanel( 'templateVars', getDict().create, vscode.ViewColumn.Active, { enableScripts: true } ); let currentVars: string[] = []; let currentTemplate = templates[0] || ''; let disposed = false; function getVarsHtml(vars: string[], values: Record = {}) { const dict = getDict(); if (!vars.length) return ''; return `

${dict.enterVariables}

${dict.varInputHint}
${vars.map(v => `

`).join('')}
`; } function getTemplatesRadioHtml(templates: string[], selected: string) { const dict = getDict(); return `

${dict.chooseTemplate}:

${templates.map(t => ` `).join('')}
`; } function getLanguageSelectorHtml(selected: string) { return ``; } function setHtml(templatesHtml: string, varsHtml: string) { const dict = getDict(); panel.webview.html = ` ${dict.create}

${dict.create}

${getLanguageSelectorHtml(language)}
${templatesHtml}
${varsHtml}
`; } // Инициализация: сразу выбран первый шаблон и форма переменных let initialVars: string[] = []; if (currentTemplate) { const templateDir = path.join(templatesDir, currentTemplate); const allVars = getAllTemplateVariables(templateDir); initialVars = Array.from(allVars); currentVars = initialVars; } setHtml(getTemplatesRadioHtml(templates, currentTemplate), getVarsHtml(initialVars)); // Обработка сообщений panel.webview.onDidReceiveMessage( async message => { if (message.type === 'selectTemplate') { currentTemplate = message.template; if (message.language) language = message.language; if (!currentTemplate) { setHtml(getTemplatesRadioHtml(templates, ''), ''); return; } // Получаем переменные для выбранного шаблона const templateDir = path.join(templatesDir, currentTemplate); const allVars = getAllTemplateVariables(templateDir); currentVars = Array.from(allVars); setHtml(getTemplatesRadioHtml(templates, currentTemplate), getVarsHtml(currentVars)); } else if (message.type === 'changeLanguage') { if (message.language) language = message.language; currentTemplate = message.template || templates[0] || ''; // Получаем переменные для выбранного шаблона let baseVars: string[] = []; if (currentTemplate) { const templateDir = path.join(templatesDir, currentTemplate); const allVars = getAllTemplateVariables(templateDir); baseVars = Array.from(allVars); currentVars = baseVars; } setHtml(getTemplatesRadioHtml(templates, currentTemplate), getVarsHtml(currentVars)); } else if (message.type === 'submit') { if (message.language) language = message.language; if (!disposed) { disposed = true; panel.dispose(); resolve({ template: message.template, vars: message.data }); } } }, undefined, context.subscriptions ); panel.onDidDispose(() => { if (!disposed) { disposed = true; resolve(undefined); } }, null, context.subscriptions); }); } // Регистрируем кастомный helper для Handlebars Handlebars.registerHelper('getVar', function(this: Record, varName: string, modifier?: string, options?: any) { if (!varName) return ''; const vars = this; if (modifier && typeof modifier === 'string') { if (vars[`${varName}.${modifier}`]) return vars[`${varName}.${modifier}`]; if (vars[varName] && typeof vars[varName] === 'object' && vars[varName][modifier]) { return vars[varName][modifier]; } return ''; } if (vars[varName]) { if (typeof vars[varName] === 'object' && vars[varName].value) return vars[varName].value; return vars[varName]; } return ''; }); // --- Автокомплит и подсветка для шаблонов --- function registerTemplateCompletionAndHighlight(context: vscode.ExtensionContext) { const config = readConfig(); const templatesPath = config.templatesPath || 'templates'; const workspaceFolders = vscode.workspace.workspaceFolders; if (!workspaceFolders || workspaceFolders.length === 0) return; const templatesDir = path.join(workspaceFolders[0].uri.fsPath, templatesPath); // --- Автокомплит --- const completionProvider: vscode.CompletionItemProvider = { provideCompletionItems(document, position, token, completionContext) { if (!isInTemplatesDir(document.uri.fsPath, templatesDir)) { return undefined; } const line = document.lineAt(position).text; const textBefore = line.slice(0, position.character); const match = /{{\s*([\w]+)?(?:\.([\w]*))?[^}]*$/.exec(textBefore); if (!match) return undefined; const allVars = getAllTemplateVariables(templatesDir); const items: vscode.CompletionItem[] = []; if (match[2] !== undefined) { for (const mod of Object.keys(CASE_MODIFIERS)) { if (!match[2] || mod.startsWith(match[2])) { const item = new vscode.CompletionItem(mod, vscode.CompletionItemKind.EnumMember); item.insertText = mod; items.push(item); } } } else { for (const v of allVars) { if (!match[1] || v.startsWith(match[1])) { const item = new vscode.CompletionItem(v, vscode.CompletionItemKind.Variable); item.insertText = v; items.push(item); } } } return items; } }; context.subscriptions.push( vscode.languages.registerCompletionItemProvider( '*', completionProvider, '{', '.' ) ); // --- Удалена старая подсветка через декоратор --- } function registerTemplateSemanticHighlight(context: vscode.ExtensionContext) { const config = readConfig(); const templatesPath = config.templatesPath || 'templates'; const workspaceFolders = vscode.workspace.workspaceFolders; if (!workspaceFolders || workspaceFolders.length === 0) return; const templatesDir = path.join(workspaceFolders[0].uri.fsPath, templatesPath); const legend = new vscode.SemanticTokensLegend(['bracket', 'variable', 'modifier']); context.subscriptions.push( vscode.languages.registerDocumentSemanticTokensProvider( { pattern: templatesDir + '/**' }, { provideDocumentSemanticTokens(document) { const tokens: number[] = []; for (let lineNum = 0; lineNum < document.lineCount; lineNum++) { const line = document.lineAt(lineNum).text; // Ищем все {{variable.modifier}} или {{variable}} const reg = /({{)|(}})|{{\s*([a-zA-Z0-9_]+)(?:\.([a-zA-Z0-9_]+))?\s*}}/g; let match; while ((match = reg.exec(line)) !== null) { if (match[1]) { // {{ tokens.push(lineNum, match.index, 2, 0, 0); // bracket } else if (match[2]) { // }} tokens.push(lineNum, match.index, 2, 0, 0); // bracket } else if (match[3]) { // variable (имя) const varStart = match.index + 2 + line.slice(match.index + 2).search(/\S/); // после {{ tokens.push(lineNum, varStart, match[3].length, 1, 0); // variable if (match[4]) { // .modifier const modStart = varStart + match[3].length + 1; // +1 за точку tokens.push(lineNum, modStart, match[4].length, 2, 0); // modifier } } } } return new vscode.SemanticTokens(new Uint32Array(tokens)); } }, legend ) ); } // === Декораторы для шаблонных переменных === const bracketDecoration = vscode.window.createTextEditorDecorationType({ color: '#43A047', // зелёный для скобок rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed, fontWeight: 'bold' }); const variableDecoration = vscode.window.createTextEditorDecorationType({ color: '#FF9800', // оранжевый для имени переменной rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed, fontWeight: 'bold' }); const modifierDecoration = vscode.window.createTextEditorDecorationType({ color: '#00ACC1', // бирюзовый для модификатора rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed, fontWeight: 'bold' }); function updateTemplateDecorations(editor: vscode.TextEditor, templatesDir: string) { if (!editor || !isInTemplatesDir(editor.document.uri.fsPath, templatesDir)) return; const brackets: vscode.DecorationOptions[] = []; const variables: vscode.DecorationOptions[] = []; const modifiers: vscode.DecorationOptions[] = []; for (let lineNum = 0; lineNum < editor.document.lineCount; lineNum++) { const line = editor.document.lineAt(lineNum).text; // Ищем все {{variable.modifier}} или {{variable}} const reg = /{{\s*([a-zA-Z0-9_]+)(?:\.([a-zA-Z0-9_]+))?\s*}}/g; let match; while ((match = reg.exec(line)) !== null) { const start = match.index; const end = start + match[0].length; // Скобки {{ и }} brackets.push({ range: new vscode.Range(lineNum, start, lineNum, start + 2) }); brackets.push({ range: new vscode.Range(lineNum, end - 2, lineNum, end) }); // Имя переменной const varStart = start + 2 + line.slice(start + 2).search(/\S/); // после {{ variables.push({ range: new vscode.Range(lineNum, varStart, lineNum, varStart + match[1].length) }); // Модификатор (если есть) if (match[2]) { const modStart = varStart + match[1].length + 1; // +1 за точку modifiers.push({ range: new vscode.Range(lineNum, modStart, lineNum, modStart + match[2].length) }); } } } editor.setDecorations(bracketDecoration, brackets); editor.setDecorations(variableDecoration, variables); editor.setDecorations(modifierDecoration, modifiers); } function registerTemplateDecorations(context: vscode.ExtensionContext) { const config = readConfig(); const templatesPath = config.templatesPath || 'templates'; const workspaceFolders = vscode.workspace.workspaceFolders; if (!workspaceFolders || workspaceFolders.length === 0) return; const templatesDir = path.join(workspaceFolders[0].uri.fsPath, templatesPath); function decorateActiveEditor() { const editor = vscode.window.activeTextEditor; if (editor) { updateTemplateDecorations(editor, templatesDir); } } context.subscriptions.push( vscode.window.onDidChangeActiveTextEditor(editor => { if (editor) decorateActiveEditor(); }), vscode.workspace.onDidChangeTextDocument(event => { const editor = vscode.window.visibleTextEditors.find(e => e.document === event.document); if (editor) updateTemplateDecorations(editor, templatesDir); }) ); // Инициализация при активации setTimeout(() => { vscode.window.visibleTextEditors.forEach(editor => updateTemplateDecorations(editor, templatesDir)); }, 300); } function isInTemplatesDir(filePath: string, templatesDir: string): boolean { const rel = path.relative(templatesDir, filePath); return ( !rel.startsWith('..') && !path.isAbsolute(rel) ); } function clearDiagnosticsForEditor(editor: vscode.TextEditor, templatesDir: string) { if (editor && isInTemplatesDir(editor.document.uri.fsPath, templatesDir)) { vscode.languages.getDiagnostics(editor.document.uri).forEach(() => { vscode.languages.createDiagnosticCollection().set(editor.document.uri, []); }); } } function clearDiagnosticsForTemplates(context: vscode.ExtensionContext) { const config = readConfig(); const templatesPath = config.templatesPath || 'templates'; const workspaceFolders = vscode.workspace.workspaceFolders; if (!workspaceFolders || workspaceFolders.length === 0) return; const templatesDir = path.join(workspaceFolders[0].uri.fsPath, templatesPath); context.subscriptions.push( vscode.window.onDidChangeActiveTextEditor(editor => { if (editor) clearDiagnosticsForEditor(editor, templatesDir); }), vscode.workspace.onDidChangeTextDocument(event => { const editor = vscode.window.visibleTextEditors.find(e => e.document === event.document); if (editor) clearDiagnosticsForEditor(editor, templatesDir); }) ); // Инициализация при активации setTimeout(() => { vscode.window.visibleTextEditors.forEach(editor => clearDiagnosticsForEditor(editor, templatesDir)); }, 300); } // This method is called when your extension is activated // Your extension is activated the very first time the command is executed export function activate(context: vscode.ExtensionContext) { // Use the console to output diagnostic information (console.log) and errors (console.error) // This line of code will only be executed once when your extension is activated console.log('Congratulations, your extension "mytemplategenerator" is now active!'); // The command has been defined in the package.json file // Now provide the implementation of the command with registerCommand // The commandId parameter must match the command field in package.json const disposable = vscode.commands.registerCommand('mytemplategenerator.helloWorld', () => { // The code you place here will be executed every time your command is executed // Display a message box to the user vscode.window.showInformationMessage('Hello World from myTemplateGenerator!'); }); const createFromTemplate = vscode.commands.registerCommand('mytemplategenerator.createFromTemplate', async (uri: vscode.Uri) => { const config = readConfig(); const dict = I18N_DICTIONARIES[config.language || 'ru'] || I18N_DICTIONARIES['ru']; const workspaceFolders = vscode.workspace.workspaceFolders; if (!workspaceFolders || workspaceFolders.length === 0) { vscode.window.showErrorMessage(dict.noFolders); return; } const templatesDir = path.join(workspaceFolders[0].uri.fsPath, config.templatesPath); if (!fs.existsSync(templatesDir) || !fs.statSync(templatesDir).isDirectory()) { vscode.window.showErrorMessage(`${dict.templatesNotFound} ${templatesDir}`); return; } let template: string | undefined; let userVars: Record | undefined; if (config.inputMode === 'webview') { const result = await showTemplateAndVarsWebview(context, templatesDir, uri.fsPath, config.language || 'ru'); if (!result) return; template = result.template; userVars = result.vars; } else { template = await pickTemplate(templatesDir); if (!template) return; const templateDir = path.join(templatesDir, template); const allVars = getAllTemplateVariables(templateDir); const baseVars = Array.from(allVars); userVars = await collectUserVars(new Set(baseVars)); } if (!template || !userVars) return; const templateDir = path.join(templatesDir, template); try { const vars = buildVarsObject(userVars); copyTemplateWithVars(templateDir, uri.fsPath, vars, config.overwriteFiles, dict, template); } catch (e: any) { vscode.window.showErrorMessage(`${dict.createError}: ${e.message}`); } }); context.subscriptions.push( disposable, createFromTemplate, vscode.commands.registerCommand('mytemplategenerator.configure', async () => { await showConfigWebview(context); }) ); registerTemplateCompletionAndHighlight(context); registerTemplateSemanticHighlight(context); registerTemplateDecorations(context); // <--- Добавить регистрацию декораторов clearDiagnosticsForTemplates(context); // <--- Очищаем diagnostics для шаблонов } // This method is called when your extension is deactivated export function deactivate() {} export { toPascalCase, toCamelCase, toSnakeCase, toKebabCase, toScreamingSnakeCase, toUpperCaseFirst, toUpperCaseAll, toLowerCaseAll, buildVarsObject, CASE_MODIFIERS };