From 5264083cf368287b70533885d4170a43f19b447c Mon Sep 17 00:00:00 2001 From: N1ebieski Date: Sun, 30 Mar 2025 12:17:28 +0000 Subject: [PATCH 01/12] Support for autocompletion model attributes in blade files Fixes N1ebieski/vs-code-extension#31 --- generatable.json | 6 +++ package.json | 6 +++ src/completion/Registry.ts | 13 ++++++ src/extension.ts | 6 +++ src/features/model.ts | 74 ++++++++++++++++++++++++++++++++ src/index.d.ts | 1 + src/parser/AutocompleteResult.ts | 13 ++++++ src/repositories/models.ts | 21 +++++++++ src/support/generated-config.ts | 2 +- src/support/str.ts | 12 ++++++ src/types.ts | 11 ++++- 11 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 src/features/model.ts create mode 100644 src/support/str.ts diff --git a/generatable.json b/generatable.json index fe1a9b6f..ad0d91b0 100644 --- a/generatable.json +++ b/generatable.json @@ -68,5 +68,11 @@ "completion", "diagnostics" ] + }, + { + "type": "model", + "features": [ + "completion" + ] } ] diff --git a/package.json b/package.json index cf77b4ec..05f65c74 100644 --- a/package.json +++ b/package.json @@ -374,6 +374,12 @@ "generated": true, "description": "Enable completion for mix." }, + "Laravel.model.completion": { + "type": "boolean", + "default": true, + "generated": true, + "description": "Enable completion for model." + }, "Laravel.paths.link": { "type": "boolean", "default": true, diff --git a/src/completion/Registry.ts b/src/completion/Registry.ts index aa42a9a1..9a0b680a 100644 --- a/src/completion/Registry.ts +++ b/src/completion/Registry.ts @@ -82,6 +82,18 @@ export default class Registry implements vscode.CompletionItemProvider { ); }; + const hasName = (names: FeatureTagParam["name"]) => { + if (typeof names === "undefined" || names === null) { + return parseResult.name() === null; + } + + if (typeof names === "string") { + return names === parseResult.name(); + } + + return names.find((fn) => fn === parseResult.name()) !== undefined; + }; + const isArgumentIndex = ( argumentIndex: number | number[] | undefined, ) => { @@ -117,6 +129,7 @@ export default class Registry implements vscode.CompletionItemProvider { (tag) => hasClass(tag.class) && hasFunc(tag.method) && + hasName(tag.name) && isArgumentIndex(tag.argumentIndex) && isNamedArg(tag.argumentName), ); diff --git a/src/extension.ts b/src/extension.ts index 9be7f982..8a78fd8f 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -17,6 +17,7 @@ import { updateDiagnostics } from "./diagnostic/diagnostic"; import { completionProvider as bladeComponentCompletion } from "./features/bladeComponent"; import { viteEnvCodeActionProvider } from "./features/env"; import { completionProvider as livewireComponentCompletion } from "./features/livewireComponent"; +import { completionProvider as modelCompletion } from "./features/model"; import { hoverProviders } from "./hover/HoverProvider"; import { linkProviders } from "./link/LinkProvider"; import { configAffected } from "./support/config"; @@ -113,6 +114,11 @@ export function activate(context: vscode.ExtensionContext) { // documentSelector, // new BladeFormattingEditProvider(), // ), + vscode.languages.registerCompletionItemProvider( + BLADE_LANGUAGES, + new Registry(modelCompletion), + ">", + ), vscode.languages.registerCompletionItemProvider( LANGUAGES, delegatedRegistry, diff --git a/src/features/model.ts b/src/features/model.ts new file mode 100644 index 00000000..31527d97 --- /dev/null +++ b/src/features/model.ts @@ -0,0 +1,74 @@ +import AutocompleteResult from "@src/parser/AutocompleteResult"; +import { getModelByName, getModels } from "@src/repositories/models"; +import { config } from "@src/support/config"; +import { camel, snake } from "@src/support/str"; +import * as vscode from "vscode"; +import { + CompletionProvider, + Eloquent, + FeatureTag, +} from ".."; + +export const completionProvider: CompletionProvider = { + tags() { + return Object.values(getModels().items).flatMap(model => { + const modelName = model.class.split("\\").pop(); + + if (!modelName) { + return null; + } + + const modelNames = [ + modelName, + modelName.toLowerCase(), + camel(modelName), + snake(modelName) + ]; + + return [ + { + method: [...modelNames], + }, + { + name: [...modelNames] + } + ]; + }).filter(item => item !== null) as FeatureTag; + }, + + provideCompletionItems( + result: AutocompleteResult, + ): vscode.CompletionItem[] { + if (!config("model.completion", true)) { + return []; + } + + const name = result.name() ?? result.func(); + + if (!name) { + return []; + } + + const model = getModelByName(name); + + if (!model) { + return []; + } + + const createCompleteItem = (item: Eloquent.Attribute | Eloquent.Relation) => { + let completeItem = new vscode.CompletionItem( + item.name, + vscode.CompletionItemKind.Property, + ); + + if (item.type) { + completeItem.detail = item.type; + } + + return completeItem; + }; + + return model.attributes.map(createCompleteItem) + .concat(model.relations.map(createCompleteItem)); + }, +}; \ No newline at end of file diff --git a/src/index.d.ts b/src/index.d.ts index dbc537e8..ca966007 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -48,6 +48,7 @@ type LinkProvider = ( interface FeatureTagParam { class?: string | string[] | null; method?: string | string[] | null; + name?: string | string[] | null; argumentName?: string | string[]; classDefinition?: string; methodDefinition?: string; diff --git a/src/parser/AutocompleteResult.ts b/src/parser/AutocompleteResult.ts index d7c251be..7d5b3be3 100644 --- a/src/parser/AutocompleteResult.ts +++ b/src/parser/AutocompleteResult.ts @@ -40,6 +40,19 @@ export default class AutocompleteResult { return this.param()?.autocompletingValue ?? false; } + public name() { + // @ts-ignore + return this.result.name ?? null; + } + + public isName(name: string | string[]) { + if (Array.isArray(name)) { + return name.includes(this.name()); + } + + return this.name() === name; + } + public class() { // @ts-ignore return this.result.className ?? null; diff --git a/src/repositories/models.ts b/src/repositories/models.ts index 3d045a3c..48a2cfb3 100644 --- a/src/repositories/models.ts +++ b/src/repositories/models.ts @@ -1,3 +1,4 @@ +import { camel, snake } from "@src/support/str"; import { repository } from "."; import { Eloquent } from ".."; import { writeEloquentDocBlocks } from "../support/docblocks"; @@ -22,6 +23,26 @@ const load = () => { }); }; + +export const getModelByName = (name: string): Eloquent.Model | undefined => { + const model = Object.entries(getModels().items).find(([key]) => { + const modelName = key.split("\\").pop(); + + if (!modelName) { + return undefined; + } + + return [ + modelName, + modelName.toLowerCase(), + camel(modelName), + snake(modelName) + ].includes(name); + }); + + return model?.[1]; +}; + export const getModels = repository({ load, pattern: modelPaths diff --git a/src/support/generated-config.ts b/src/support/generated-config.ts index f5758a9d..7dd96fe0 100644 --- a/src/support/generated-config.ts +++ b/src/support/generated-config.ts @@ -1 +1 @@ -export type GeneratedConfigKey = 'appBinding.diagnostics' | 'appBinding.hover' | 'appBinding.link' | 'appBinding.completion' | 'asset.diagnostics' | 'asset.hover' | 'asset.link' | 'asset.completion' | 'auth.diagnostics' | 'auth.hover' | 'auth.link' | 'auth.completion' | 'bladeComponent.link' | 'bladeComponent.completion' | 'bladeComponent.hover' | 'config.diagnostics' | 'config.hover' | 'config.link' | 'config.completion' | 'controllerAction.diagnostics' | 'controllerAction.hover' | 'controllerAction.link' | 'controllerAction.completion' | 'env.diagnostics' | 'env.hover' | 'env.link' | 'env.completion' | 'inertia.diagnostics' | 'inertia.hover' | 'inertia.link' | 'inertia.completion' | 'livewireComponent.link' | 'livewireComponent.completion' | 'middleware.diagnostics' | 'middleware.hover' | 'middleware.link' | 'middleware.completion' | 'mix.diagnostics' | 'mix.hover' | 'mix.link' | 'mix.completion' | 'paths.link' | 'route.diagnostics' | 'route.hover' | 'route.link' | 'route.completion' | 'storage.link' | 'storage.completion' | 'storage.diagnostics' | 'translation.diagnostics' | 'translation.hover' | 'translation.link' | 'translation.completion' | 'view.diagnostics' | 'view.hover' | 'view.link' | 'view.completion'; +export type GeneratedConfigKey = 'appBinding.diagnostics' | 'appBinding.hover' | 'appBinding.link' | 'appBinding.completion' | 'asset.diagnostics' | 'asset.hover' | 'asset.link' | 'asset.completion' | 'auth.diagnostics' | 'auth.hover' | 'auth.link' | 'auth.completion' | 'bladeComponent.link' | 'bladeComponent.completion' | 'bladeComponent.hover' | 'config.diagnostics' | 'config.hover' | 'config.link' | 'config.completion' | 'controllerAction.diagnostics' | 'controllerAction.hover' | 'controllerAction.link' | 'controllerAction.completion' | 'env.diagnostics' | 'env.hover' | 'env.link' | 'env.completion' | 'inertia.diagnostics' | 'inertia.hover' | 'inertia.link' | 'inertia.completion' | 'livewireComponent.link' | 'livewireComponent.completion' | 'middleware.diagnostics' | 'middleware.hover' | 'middleware.link' | 'middleware.completion' | 'mix.diagnostics' | 'mix.hover' | 'mix.link' | 'mix.completion' | 'model.completion' | 'paths.link' | 'route.diagnostics' | 'route.hover' | 'route.link' | 'route.completion' | 'storage.link' | 'storage.completion' | 'storage.diagnostics' | 'translation.diagnostics' | 'translation.hover' | 'translation.link' | 'translation.completion' | 'view.diagnostics' | 'view.hover' | 'view.link' | 'view.completion'; diff --git a/src/support/str.ts b/src/support/str.ts new file mode 100644 index 00000000..96666afc --- /dev/null +++ b/src/support/str.ts @@ -0,0 +1,12 @@ +export const camel = (str: string): string => { + return str + .replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : '') // Removing separators and converting to uppercase + .replace(/^(.)/, (c) => c.toLowerCase()); // First letter lowercase +}; + +export const snake = (str: string): string => { + return str + .replace(/([a-z])([A-Z])/g, '$1_$2') // Separation of camelCase + .replace(/[-\s]+/g, '_') // Converting spaces and dashes to underscores + .toLowerCase(); +}; \ No newline at end of file diff --git a/src/types.ts b/src/types.ts index a4419bab..c38f4bad 100644 --- a/src/types.ts +++ b/src/types.ts @@ -15,7 +15,8 @@ export namespace AutocompleteParsingResult { | Parameter | ParameterValue | Parameters - | StringValue; + | StringValue + | Variable; export interface Argument { type: "argument"; @@ -143,4 +144,10 @@ export namespace AutocompleteParsingResult { column: number; }; } -} + + export interface Variable { + type: "variable"; + parent: ContextValue | null; + name: string | null; + } +} \ No newline at end of file From 1fae60c8d9a73f5658ce769304bf061da57e6971 Mon Sep 17 00:00:00 2001 From: N1ebieski Date: Sun, 30 Mar 2025 21:10:20 +0000 Subject: [PATCH 02/12] refactoring --- src/features/model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/model.ts b/src/features/model.ts index 31527d97..e8177f84 100644 --- a/src/features/model.ts +++ b/src/features/model.ts @@ -33,7 +33,7 @@ export const completionProvider: CompletionProvider = { name: [...modelNames] } ]; - }).filter(item => item !== null) as FeatureTag; + }).filter(item => !item) as FeatureTag; }, provideCompletionItems( From 8e910bb9abb71eaf06bb4a45501564d9e10e03f2 Mon Sep 17 00:00:00 2001 From: N1ebieski Date: Sun, 30 Mar 2025 21:20:33 +0000 Subject: [PATCH 03/12] Revert "refactoring" This reverts commit 1fae60c8d9a73f5658ce769304bf061da57e6971. --- src/features/model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/model.ts b/src/features/model.ts index e8177f84..31527d97 100644 --- a/src/features/model.ts +++ b/src/features/model.ts @@ -33,7 +33,7 @@ export const completionProvider: CompletionProvider = { name: [...modelNames] } ]; - }).filter(item => !item) as FeatureTag; + }).filter(item => item !== null) as FeatureTag; }, provideCompletionItems( From e81825b08ce6848635b4374cd6c7d5e1ba7c7369 Mon Sep 17 00:00:00 2001 From: N1ebieski Date: Fri, 11 Apr 2025 08:01:21 +0000 Subject: [PATCH 04/12] Move name cases (camel, snake etc.) from javascript support helpers to the model php template --- php-templates/models.php | 17 +++++++++++++++++ src/features/model.ts | 17 ++--------------- src/index.d.ts | 2 ++ src/repositories/models.ts | 16 +++------------- src/templates/models.ts | 17 +++++++++++++++++ 5 files changed, 41 insertions(+), 28 deletions(-) diff --git a/php-templates/models.php b/php-templates/models.php index 3946d402..f8afdb8f 100644 --- a/php-templates/models.php +++ b/php-templates/models.php @@ -144,11 +144,14 @@ protected function getInfo($className) $data["extends"] = $this->getParentClass($reflection); + $data['name_cases'] = $this->getNameCases(str($className)->afterLast('\\')->toString()); + $existingProperties = $this->collectExistingProperties($reflection); $data['attributes'] = collect($data['attributes']) ->map(fn($attrs) => array_merge($attrs, [ 'title_case' => str($attrs['name'])->title()->replace('_', '')->toString(), + 'name_cases' => $this->getNameCases($attrs['name']), 'documented' => $existingProperties->contains($attrs['name']), 'cast' => $this->getCastReturnType($attrs['cast']) ])) @@ -166,6 +169,20 @@ protected function getInfo($className) $className => $data, ]; } + + /** + * @return array + */ + protected function getNameCases(string $name): array + { + return collect([ + $name, + str($name)->camel()->toString(), + str($name)->snake()->toString(), + str($name)->studly()->toString(), + str($name)->studly()->lower()->toString(), + ])->unique()->values()->toArray(); + } }; $builder = new class($docblocks) { diff --git a/src/features/model.ts b/src/features/model.ts index 31527d97..67ec6786 100644 --- a/src/features/model.ts +++ b/src/features/model.ts @@ -12,25 +12,12 @@ import { export const completionProvider: CompletionProvider = { tags() { return Object.values(getModels().items).flatMap(model => { - const modelName = model.class.split("\\").pop(); - - if (!modelName) { - return null; - } - - const modelNames = [ - modelName, - modelName.toLowerCase(), - camel(modelName), - snake(modelName) - ]; - return [ { - method: [...modelNames], + method: [...model.name_cases], }, { - name: [...modelNames] + name: [...model.name_cases] } ]; }).filter(item => item !== null) as FeatureTag; diff --git a/src/index.d.ts b/src/index.d.ts index ca966007..8b2b7f54 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -84,6 +84,7 @@ declare namespace Eloquent { observers: Observer[]; scopes: string[]; extends: string | null; + name_cases: string[]; } interface Attribute { @@ -98,6 +99,7 @@ declare namespace Eloquent { appended: null; cast: string | null; title_case: string; + name_cases: string[]; documented: boolean; } diff --git a/src/repositories/models.ts b/src/repositories/models.ts index 48a2cfb3..f1285350 100644 --- a/src/repositories/models.ts +++ b/src/repositories/models.ts @@ -24,20 +24,10 @@ const load = () => { }; -export const getModelByName = (name: string): Eloquent.Model | undefined => { - const model = Object.entries(getModels().items).find(([key]) => { - const modelName = key.split("\\").pop(); - - if (!modelName) { - return undefined; - } - return [ - modelName, - modelName.toLowerCase(), - camel(modelName), - snake(modelName) - ].includes(name); +export const getModelByName = (name: string): Eloquent.Model | undefined => { + const model = Object.entries(getModels().items).find(([, value]) => { + return value.name_cases.includes(name); }); return model?.[1]; diff --git a/src/templates/models.ts b/src/templates/models.ts index 158b4952..9bcface8 100644 --- a/src/templates/models.ts +++ b/src/templates/models.ts @@ -144,11 +144,14 @@ $models = new class($factory) { $data["extends"] = $this->getParentClass($reflection); + $data['name_cases'] = $this->getNameCases(str($className)->afterLast('\\\\')->toString()); + $existingProperties = $this->collectExistingProperties($reflection); $data['attributes'] = collect($data['attributes']) ->map(fn($attrs) => array_merge($attrs, [ 'title_case' => str($attrs['name'])->title()->replace('_', '')->toString(), + 'name_cases' => $this->getNameCases($attrs['name']), 'documented' => $existingProperties->contains($attrs['name']), 'cast' => $this->getCastReturnType($attrs['cast']) ])) @@ -166,6 +169,20 @@ $models = new class($factory) { $className => $data, ]; } + + /** + * @return array + */ + protected function getNameCases(string $name): array + { + return collect([ + $name, + str($name)->camel()->toString(), + str($name)->snake()->toString(), + str($name)->studly()->toString(), + str($name)->studly()->lower()->toString(), + ])->unique()->values()->toArray(); + } }; $builder = new class($docblocks) { From 3b579626c4cdc5edafe3ff3fd6d9534fef521dd0 Mon Sep 17 00:00:00 2001 From: N1ebieski Date: Fri, 11 Apr 2025 08:26:36 +0000 Subject: [PATCH 05/12] model completion --- generatable.json | 3 ++- package.json | 10 ++++++++-- src/extension.ts | 9 +++++++-- src/features/model.ts | 34 +++++++++++++++++++++++++++++++-- src/support/generated-config.ts | 2 +- 5 files changed, 50 insertions(+), 8 deletions(-) diff --git a/generatable.json b/generatable.json index ad0d91b0..251508f1 100644 --- a/generatable.json +++ b/generatable.json @@ -72,7 +72,8 @@ { "type": "model", "features": [ - "completion" + "completion_property", + "completion_model" ] } ] diff --git a/package.json b/package.json index 5ccc7257..065c9a1f 100644 --- a/package.json +++ b/package.json @@ -374,11 +374,17 @@ "generated": true, "description": "Enable completion for mix." }, - "Laravel.model.completion": { + "Laravel.model.completion_property": { "type": "boolean", "default": true, "generated": true, - "description": "Enable completion for model." + "description": null + }, + "Laravel.model.completion_model": { + "type": "boolean", + "default": true, + "generated": true, + "description": null }, "Laravel.paths.link": { "type": "boolean", diff --git a/src/extension.ts b/src/extension.ts index 8a78fd8f..7851ee14 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -17,7 +17,7 @@ import { updateDiagnostics } from "./diagnostic/diagnostic"; import { completionProvider as bladeComponentCompletion } from "./features/bladeComponent"; import { viteEnvCodeActionProvider } from "./features/env"; import { completionProvider as livewireComponentCompletion } from "./features/livewireComponent"; -import { completionProvider as modelCompletion } from "./features/model"; +import { completionModelProvider, completionPropertyProvider } from "./features/model"; import { hoverProviders } from "./hover/HoverProvider"; import { linkProviders } from "./link/LinkProvider"; import { configAffected } from "./support/config"; @@ -116,9 +116,14 @@ export function activate(context: vscode.ExtensionContext) { // ), vscode.languages.registerCompletionItemProvider( BLADE_LANGUAGES, - new Registry(modelCompletion), + new Registry(completionPropertyProvider), ">", ), + vscode.languages.registerCompletionItemProvider( + BLADE_LANGUAGES, + completionModelProvider, + "$", + ), vscode.languages.registerCompletionItemProvider( LANGUAGES, delegatedRegistry, diff --git a/src/features/model.ts b/src/features/model.ts index 67ec6786..038c7ff7 100644 --- a/src/features/model.ts +++ b/src/features/model.ts @@ -9,7 +9,37 @@ import { FeatureTag, } from ".."; -export const completionProvider: CompletionProvider = { +export const completionModelProvider: vscode.CompletionItemProvider = { + provideCompletionItems( + doc: vscode.TextDocument, + pos: vscode.Position, + ): vscode.ProviderResult { + if (!config("model.completion_model", true)) { + return undefined; + } + + const line = doc.lineAt(pos.line).text; + const linePrefix = line.substring(pos.character - 1, pos.character); + + if (linePrefix !== '$') { + return undefined; + } + + console.log('models', Object.entries(getModels().items).flatMap(([, value]) => { + return value.name_cases.map((name) => { + return new vscode.CompletionItem(name); + }); + })); + + return Object.entries(getModels().items).flatMap(([, value]) => { + return value.name_cases.map((name) => { + return new vscode.CompletionItem(name); + }); + }); + }, +}; + +export const completionPropertyProvider: CompletionProvider = { tags() { return Object.values(getModels().items).flatMap(model => { return [ @@ -26,7 +56,7 @@ export const completionProvider: CompletionProvider = { provideCompletionItems( result: AutocompleteResult, ): vscode.CompletionItem[] { - if (!config("model.completion", true)) { + if (!config("model.completion_property", true)) { return []; } diff --git a/src/support/generated-config.ts b/src/support/generated-config.ts index 7dd96fe0..edc54e7b 100644 --- a/src/support/generated-config.ts +++ b/src/support/generated-config.ts @@ -1 +1 @@ -export type GeneratedConfigKey = 'appBinding.diagnostics' | 'appBinding.hover' | 'appBinding.link' | 'appBinding.completion' | 'asset.diagnostics' | 'asset.hover' | 'asset.link' | 'asset.completion' | 'auth.diagnostics' | 'auth.hover' | 'auth.link' | 'auth.completion' | 'bladeComponent.link' | 'bladeComponent.completion' | 'bladeComponent.hover' | 'config.diagnostics' | 'config.hover' | 'config.link' | 'config.completion' | 'controllerAction.diagnostics' | 'controllerAction.hover' | 'controllerAction.link' | 'controllerAction.completion' | 'env.diagnostics' | 'env.hover' | 'env.link' | 'env.completion' | 'inertia.diagnostics' | 'inertia.hover' | 'inertia.link' | 'inertia.completion' | 'livewireComponent.link' | 'livewireComponent.completion' | 'middleware.diagnostics' | 'middleware.hover' | 'middleware.link' | 'middleware.completion' | 'mix.diagnostics' | 'mix.hover' | 'mix.link' | 'mix.completion' | 'model.completion' | 'paths.link' | 'route.diagnostics' | 'route.hover' | 'route.link' | 'route.completion' | 'storage.link' | 'storage.completion' | 'storage.diagnostics' | 'translation.diagnostics' | 'translation.hover' | 'translation.link' | 'translation.completion' | 'view.diagnostics' | 'view.hover' | 'view.link' | 'view.completion'; +export type GeneratedConfigKey = 'appBinding.diagnostics' | 'appBinding.hover' | 'appBinding.link' | 'appBinding.completion' | 'asset.diagnostics' | 'asset.hover' | 'asset.link' | 'asset.completion' | 'auth.diagnostics' | 'auth.hover' | 'auth.link' | 'auth.completion' | 'bladeComponent.link' | 'bladeComponent.completion' | 'bladeComponent.hover' | 'config.diagnostics' | 'config.hover' | 'config.link' | 'config.completion' | 'controllerAction.diagnostics' | 'controllerAction.hover' | 'controllerAction.link' | 'controllerAction.completion' | 'env.diagnostics' | 'env.hover' | 'env.link' | 'env.completion' | 'inertia.diagnostics' | 'inertia.hover' | 'inertia.link' | 'inertia.completion' | 'livewireComponent.link' | 'livewireComponent.completion' | 'middleware.diagnostics' | 'middleware.hover' | 'middleware.link' | 'middleware.completion' | 'mix.diagnostics' | 'mix.hover' | 'mix.link' | 'mix.completion' | 'model.completion_property' | 'model.completion_model' | 'paths.link' | 'route.diagnostics' | 'route.hover' | 'route.link' | 'route.completion' | 'storage.link' | 'storage.completion' | 'storage.diagnostics' | 'translation.diagnostics' | 'translation.hover' | 'translation.link' | 'translation.completion' | 'view.diagnostics' | 'view.hover' | 'view.link' | 'view.completion'; From 55b1c24acdea4c30da6a3bec047bfd351a533342 Mon Sep 17 00:00:00 2001 From: N1ebieski Date: Fri, 11 Apr 2025 09:15:39 +0000 Subject: [PATCH 06/12] Support for autocompletion model in blade files Fixes N1ebieski/vs-code-extension#31 --- php-templates/models.php | 10 ++++++++-- src/features/model.ts | 23 +++-------------------- src/repositories/models.ts | 1 - src/templates/models.ts | 10 ++++++++-- 4 files changed, 19 insertions(+), 25 deletions(-) diff --git a/php-templates/models.php b/php-templates/models.php index f8afdb8f..8f8db405 100644 --- a/php-templates/models.php +++ b/php-templates/models.php @@ -144,7 +144,13 @@ protected function getInfo($className) $data["extends"] = $this->getParentClass($reflection); - $data['name_cases'] = $this->getNameCases(str($className)->afterLast('\\')->toString()); + $name = str($className)->afterLast('\\'); + + $data['name_cases'] = array_merge(...array_map( + null, + $this->getNameCases($name->toString()), + $this->getNameCases($name->plural()->toString()) + )); $existingProperties = $this->collectExistingProperties($reflection); @@ -176,8 +182,8 @@ protected function getInfo($className) protected function getNameCases(string $name): array { return collect([ - $name, str($name)->camel()->toString(), + $name, str($name)->snake()->toString(), str($name)->studly()->toString(), str($name)->studly()->lower()->toString(), diff --git a/src/features/model.ts b/src/features/model.ts index 038c7ff7..47e7d63f 100644 --- a/src/features/model.ts +++ b/src/features/model.ts @@ -1,7 +1,6 @@ import AutocompleteResult from "@src/parser/AutocompleteResult"; import { getModelByName, getModels } from "@src/repositories/models"; import { config } from "@src/support/config"; -import { camel, snake } from "@src/support/str"; import * as vscode from "vscode"; import { CompletionProvider, @@ -10,30 +9,14 @@ import { } from ".."; export const completionModelProvider: vscode.CompletionItemProvider = { - provideCompletionItems( - doc: vscode.TextDocument, - pos: vscode.Position, - ): vscode.ProviderResult { + provideCompletionItems(): vscode.ProviderResult { if (!config("model.completion_model", true)) { return undefined; } - const line = doc.lineAt(pos.line).text; - const linePrefix = line.substring(pos.character - 1, pos.character); - - if (linePrefix !== '$') { - return undefined; - } - - console.log('models', Object.entries(getModels().items).flatMap(([, value]) => { - return value.name_cases.map((name) => { - return new vscode.CompletionItem(name); - }); - })); - return Object.entries(getModels().items).flatMap(([, value]) => { - return value.name_cases.map((name) => { - return new vscode.CompletionItem(name); + return value.name_cases.slice(0, 2).map((name) => { + return new vscode.CompletionItem(name, vscode.CompletionItemKind.Variable); }); }); }, diff --git a/src/repositories/models.ts b/src/repositories/models.ts index f1285350..4746a65e 100644 --- a/src/repositories/models.ts +++ b/src/repositories/models.ts @@ -1,4 +1,3 @@ -import { camel, snake } from "@src/support/str"; import { repository } from "."; import { Eloquent } from ".."; import { writeEloquentDocBlocks } from "../support/docblocks"; diff --git a/src/templates/models.ts b/src/templates/models.ts index 9bcface8..74677557 100644 --- a/src/templates/models.ts +++ b/src/templates/models.ts @@ -144,7 +144,13 @@ $models = new class($factory) { $data["extends"] = $this->getParentClass($reflection); - $data['name_cases'] = $this->getNameCases(str($className)->afterLast('\\\\')->toString()); + $name = str($className)->afterLast('\\\\'); + + $data['name_cases'] = array_merge(...array_map( + null, + $this->getNameCases($name->toString()), + $this->getNameCases($name->plural()->toString()) + )); $existingProperties = $this->collectExistingProperties($reflection); @@ -176,8 +182,8 @@ $models = new class($factory) { protected function getNameCases(string $name): array { return collect([ - $name, str($name)->camel()->toString(), + $name, str($name)->snake()->toString(), str($name)->studly()->toString(), str($name)->studly()->lower()->toString(), From 92fd15257c918d67f75275a858be35e588961ad6 Mon Sep 17 00:00:00 2001 From: N1ebieski Date: Fri, 11 Apr 2025 09:46:54 +0000 Subject: [PATCH 07/12] refactoring --- src/repositories/models.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/repositories/models.ts b/src/repositories/models.ts index 4746a65e..411618ce 100644 --- a/src/repositories/models.ts +++ b/src/repositories/models.ts @@ -22,8 +22,6 @@ const load = () => { }); }; - - export const getModelByName = (name: string): Eloquent.Model | undefined => { const model = Object.entries(getModels().items).find(([, value]) => { return value.name_cases.includes(name); From 4ba751f9047e98b5dfaef33058a4703fff6be714 Mon Sep 17 00:00:00 2001 From: N1ebieski Date: Sat, 12 Apr 2025 08:46:31 +0000 Subject: [PATCH 08/12] rename property to attribute --- generatable.json | 2 +- package.json | 2 +- src/extension.ts | 4 ++-- src/features/model.ts | 4 ++-- src/support/generated-config.ts | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/generatable.json b/generatable.json index 251508f1..3aa88ccf 100644 --- a/generatable.json +++ b/generatable.json @@ -72,7 +72,7 @@ { "type": "model", "features": [ - "completion_property", + "completion_attribute", "completion_model" ] } diff --git a/package.json b/package.json index 065c9a1f..25db63f9 100644 --- a/package.json +++ b/package.json @@ -374,7 +374,7 @@ "generated": true, "description": "Enable completion for mix." }, - "Laravel.model.completion_property": { + "Laravel.model.completion_attribute": { "type": "boolean", "default": true, "generated": true, diff --git a/src/extension.ts b/src/extension.ts index 7851ee14..60ff058a 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -17,7 +17,7 @@ import { updateDiagnostics } from "./diagnostic/diagnostic"; import { completionProvider as bladeComponentCompletion } from "./features/bladeComponent"; import { viteEnvCodeActionProvider } from "./features/env"; import { completionProvider as livewireComponentCompletion } from "./features/livewireComponent"; -import { completionModelProvider, completionPropertyProvider } from "./features/model"; +import { completionAttributeProvider, completionModelProvider } from "./features/model"; import { hoverProviders } from "./hover/HoverProvider"; import { linkProviders } from "./link/LinkProvider"; import { configAffected } from "./support/config"; @@ -116,7 +116,7 @@ export function activate(context: vscode.ExtensionContext) { // ), vscode.languages.registerCompletionItemProvider( BLADE_LANGUAGES, - new Registry(completionPropertyProvider), + new Registry(completionAttributeProvider), ">", ), vscode.languages.registerCompletionItemProvider( diff --git a/src/features/model.ts b/src/features/model.ts index 47e7d63f..5e7b93be 100644 --- a/src/features/model.ts +++ b/src/features/model.ts @@ -22,7 +22,7 @@ export const completionModelProvider: vscode.CompletionItemProvider = { }, }; -export const completionPropertyProvider: CompletionProvider = { +export const completionAttributeProvider: CompletionProvider = { tags() { return Object.values(getModels().items).flatMap(model => { return [ @@ -39,7 +39,7 @@ export const completionPropertyProvider: CompletionProvider = { provideCompletionItems( result: AutocompleteResult, ): vscode.CompletionItem[] { - if (!config("model.completion_property", true)) { + if (!config("model.completion_attribute", true)) { return []; } diff --git a/src/support/generated-config.ts b/src/support/generated-config.ts index edc54e7b..3988ad57 100644 --- a/src/support/generated-config.ts +++ b/src/support/generated-config.ts @@ -1 +1 @@ -export type GeneratedConfigKey = 'appBinding.diagnostics' | 'appBinding.hover' | 'appBinding.link' | 'appBinding.completion' | 'asset.diagnostics' | 'asset.hover' | 'asset.link' | 'asset.completion' | 'auth.diagnostics' | 'auth.hover' | 'auth.link' | 'auth.completion' | 'bladeComponent.link' | 'bladeComponent.completion' | 'bladeComponent.hover' | 'config.diagnostics' | 'config.hover' | 'config.link' | 'config.completion' | 'controllerAction.diagnostics' | 'controllerAction.hover' | 'controllerAction.link' | 'controllerAction.completion' | 'env.diagnostics' | 'env.hover' | 'env.link' | 'env.completion' | 'inertia.diagnostics' | 'inertia.hover' | 'inertia.link' | 'inertia.completion' | 'livewireComponent.link' | 'livewireComponent.completion' | 'middleware.diagnostics' | 'middleware.hover' | 'middleware.link' | 'middleware.completion' | 'mix.diagnostics' | 'mix.hover' | 'mix.link' | 'mix.completion' | 'model.completion_property' | 'model.completion_model' | 'paths.link' | 'route.diagnostics' | 'route.hover' | 'route.link' | 'route.completion' | 'storage.link' | 'storage.completion' | 'storage.diagnostics' | 'translation.diagnostics' | 'translation.hover' | 'translation.link' | 'translation.completion' | 'view.diagnostics' | 'view.hover' | 'view.link' | 'view.completion'; +export type GeneratedConfigKey = 'appBinding.diagnostics' | 'appBinding.hover' | 'appBinding.link' | 'appBinding.completion' | 'asset.diagnostics' | 'asset.hover' | 'asset.link' | 'asset.completion' | 'auth.diagnostics' | 'auth.hover' | 'auth.link' | 'auth.completion' | 'bladeComponent.link' | 'bladeComponent.completion' | 'bladeComponent.hover' | 'config.diagnostics' | 'config.hover' | 'config.link' | 'config.completion' | 'controllerAction.diagnostics' | 'controllerAction.hover' | 'controllerAction.link' | 'controllerAction.completion' | 'env.diagnostics' | 'env.hover' | 'env.link' | 'env.completion' | 'inertia.diagnostics' | 'inertia.hover' | 'inertia.link' | 'inertia.completion' | 'livewireComponent.link' | 'livewireComponent.completion' | 'middleware.diagnostics' | 'middleware.hover' | 'middleware.link' | 'middleware.completion' | 'mix.diagnostics' | 'mix.hover' | 'mix.link' | 'mix.completion' | 'model.completion_attribute' | 'model.completion_model' | 'paths.link' | 'route.diagnostics' | 'route.hover' | 'route.link' | 'route.completion' | 'storage.link' | 'storage.completion' | 'storage.diagnostics' | 'translation.diagnostics' | 'translation.hover' | 'translation.link' | 'translation.completion' | 'view.diagnostics' | 'view.hover' | 'view.link' | 'view.completion'; From 6bbf4d4e0317c8c79e38e97126ff6226dc5e5304 Mon Sep 17 00:00:00 2001 From: N1ebieski Date: Sat, 12 Apr 2025 08:59:39 +0000 Subject: [PATCH 09/12] refactoring config names --- generatable.json | 2 +- generate-config.php | 1 + package.json | 6 +++--- src/features/model.ts | 2 +- src/support/generated-config.ts | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/generatable.json b/generatable.json index 3aa88ccf..f548e342 100644 --- a/generatable.json +++ b/generatable.json @@ -73,7 +73,7 @@ "type": "model", "features": [ "completion_attribute", - "completion_model" + "completion" ] } ] diff --git a/generate-config.php b/generate-config.php index b87ce7cf..073e05df 100644 --- a/generate-config.php +++ b/generate-config.php @@ -24,6 +24,7 @@ 'hover' => "Enable hover information for {$label}.", 'link' => "Enable linking for {$label}.", 'completion' => "Enable completion for {$label}.", + 'completion_attribute' => "Enable completion for {$label} attributes.", default => null, }, ]; diff --git a/package.json b/package.json index 25db63f9..727145c1 100644 --- a/package.json +++ b/package.json @@ -378,13 +378,13 @@ "type": "boolean", "default": true, "generated": true, - "description": null + "description": "Enable completion for model attributes." }, - "Laravel.model.completion_model": { + "Laravel.model.completion": { "type": "boolean", "default": true, "generated": true, - "description": null + "description": "Enable completion for model." }, "Laravel.paths.link": { "type": "boolean", diff --git a/src/features/model.ts b/src/features/model.ts index 5e7b93be..57175aa0 100644 --- a/src/features/model.ts +++ b/src/features/model.ts @@ -10,7 +10,7 @@ import { export const completionModelProvider: vscode.CompletionItemProvider = { provideCompletionItems(): vscode.ProviderResult { - if (!config("model.completion_model", true)) { + if (!config("model.completion", true)) { return undefined; } diff --git a/src/support/generated-config.ts b/src/support/generated-config.ts index 3988ad57..580ad31b 100644 --- a/src/support/generated-config.ts +++ b/src/support/generated-config.ts @@ -1 +1 @@ -export type GeneratedConfigKey = 'appBinding.diagnostics' | 'appBinding.hover' | 'appBinding.link' | 'appBinding.completion' | 'asset.diagnostics' | 'asset.hover' | 'asset.link' | 'asset.completion' | 'auth.diagnostics' | 'auth.hover' | 'auth.link' | 'auth.completion' | 'bladeComponent.link' | 'bladeComponent.completion' | 'bladeComponent.hover' | 'config.diagnostics' | 'config.hover' | 'config.link' | 'config.completion' | 'controllerAction.diagnostics' | 'controllerAction.hover' | 'controllerAction.link' | 'controllerAction.completion' | 'env.diagnostics' | 'env.hover' | 'env.link' | 'env.completion' | 'inertia.diagnostics' | 'inertia.hover' | 'inertia.link' | 'inertia.completion' | 'livewireComponent.link' | 'livewireComponent.completion' | 'middleware.diagnostics' | 'middleware.hover' | 'middleware.link' | 'middleware.completion' | 'mix.diagnostics' | 'mix.hover' | 'mix.link' | 'mix.completion' | 'model.completion_attribute' | 'model.completion_model' | 'paths.link' | 'route.diagnostics' | 'route.hover' | 'route.link' | 'route.completion' | 'storage.link' | 'storage.completion' | 'storage.diagnostics' | 'translation.diagnostics' | 'translation.hover' | 'translation.link' | 'translation.completion' | 'view.diagnostics' | 'view.hover' | 'view.link' | 'view.completion'; +export type GeneratedConfigKey = 'appBinding.diagnostics' | 'appBinding.hover' | 'appBinding.link' | 'appBinding.completion' | 'asset.diagnostics' | 'asset.hover' | 'asset.link' | 'asset.completion' | 'auth.diagnostics' | 'auth.hover' | 'auth.link' | 'auth.completion' | 'bladeComponent.link' | 'bladeComponent.completion' | 'bladeComponent.hover' | 'config.diagnostics' | 'config.hover' | 'config.link' | 'config.completion' | 'controllerAction.diagnostics' | 'controllerAction.hover' | 'controllerAction.link' | 'controllerAction.completion' | 'env.diagnostics' | 'env.hover' | 'env.link' | 'env.completion' | 'inertia.diagnostics' | 'inertia.hover' | 'inertia.link' | 'inertia.completion' | 'livewireComponent.link' | 'livewireComponent.completion' | 'middleware.diagnostics' | 'middleware.hover' | 'middleware.link' | 'middleware.completion' | 'mix.diagnostics' | 'mix.hover' | 'mix.link' | 'mix.completion' | 'model.completion_attribute' | 'model.completion' | 'paths.link' | 'route.diagnostics' | 'route.hover' | 'route.link' | 'route.completion' | 'storage.link' | 'storage.completion' | 'storage.diagnostics' | 'translation.diagnostics' | 'translation.hover' | 'translation.link' | 'translation.completion' | 'view.diagnostics' | 'view.hover' | 'view.link' | 'view.completion'; From 3ca3093b6700f5b332390949f542e82001346518 Mon Sep 17 00:00:00 2001 From: N1ebieski Date: Mon, 14 Apr 2025 21:42:16 +0000 Subject: [PATCH 10/12] refactoring --- php-templates/models.php | 16 ++++++++-------- src/templates/models.ts | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/php-templates/models.php b/php-templates/models.php index 8f8db405..f475fe76 100644 --- a/php-templates/models.php +++ b/php-templates/models.php @@ -148,8 +148,8 @@ protected function getInfo($className) $data['name_cases'] = array_merge(...array_map( null, - $this->getNameCases($name->toString()), - $this->getNameCases($name->plural()->toString()) + $this->getNameCases($name), + $this->getNameCases($name->plural()) )); $existingProperties = $this->collectExistingProperties($reflection); @@ -179,14 +179,14 @@ protected function getInfo($className) /** * @return array */ - protected function getNameCases(string $name): array + protected function getNameCases(\Illuminate\Support\Stringable $name): array { return collect([ - str($name)->camel()->toString(), - $name, - str($name)->snake()->toString(), - str($name)->studly()->toString(), - str($name)->studly()->lower()->toString(), + $name->camel()->toString(), + $name->toString(), + $name->snake()->toString(), + $name->studly()->toString(), + $name->studly()->lower()->toString(), ])->unique()->values()->toArray(); } }; diff --git a/src/templates/models.ts b/src/templates/models.ts index 74677557..f8f167df 100644 --- a/src/templates/models.ts +++ b/src/templates/models.ts @@ -148,8 +148,8 @@ $models = new class($factory) { $data['name_cases'] = array_merge(...array_map( null, - $this->getNameCases($name->toString()), - $this->getNameCases($name->plural()->toString()) + $this->getNameCases($name), + $this->getNameCases($name->plural()) )); $existingProperties = $this->collectExistingProperties($reflection); @@ -179,14 +179,14 @@ $models = new class($factory) { /** * @return array */ - protected function getNameCases(string $name): array + protected function getNameCases(\\Illuminate\\Support\\Stringable $name): array { return collect([ - str($name)->camel()->toString(), - $name, - str($name)->snake()->toString(), - str($name)->studly()->toString(), - str($name)->studly()->lower()->toString(), + $name->camel()->toString(), + $name->toString(), + $name->snake()->toString(), + $name->studly()->toString(), + $name->studly()->lower()->toString(), ])->unique()->values()->toArray(); } }; From 23bd7091e5826906654c0f57057865000bf5a743 Mon Sep 17 00:00:00 2001 From: N1ebieski Date: Mon, 14 Apr 2025 21:45:14 +0000 Subject: [PATCH 11/12] refactoring --- php-templates/models.php | 4 ++-- src/templates/models.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/php-templates/models.php b/php-templates/models.php index f475fe76..fdd88655 100644 --- a/php-templates/models.php +++ b/php-templates/models.php @@ -157,7 +157,7 @@ protected function getInfo($className) $data['attributes'] = collect($data['attributes']) ->map(fn($attrs) => array_merge($attrs, [ 'title_case' => str($attrs['name'])->title()->replace('_', '')->toString(), - 'name_cases' => $this->getNameCases($attrs['name']), + 'name_cases' => $this->getNameCases(str($attrs['name'])), 'documented' => $existingProperties->contains($attrs['name']), 'cast' => $this->getCastReturnType($attrs['cast']) ])) @@ -179,7 +179,7 @@ protected function getInfo($className) /** * @return array */ - protected function getNameCases(\Illuminate\Support\Stringable $name): array + private function getNameCases(\Illuminate\Support\Stringable $name): array { return collect([ $name->camel()->toString(), diff --git a/src/templates/models.ts b/src/templates/models.ts index f8f167df..2d979920 100644 --- a/src/templates/models.ts +++ b/src/templates/models.ts @@ -157,7 +157,7 @@ $models = new class($factory) { $data['attributes'] = collect($data['attributes']) ->map(fn($attrs) => array_merge($attrs, [ 'title_case' => str($attrs['name'])->title()->replace('_', '')->toString(), - 'name_cases' => $this->getNameCases($attrs['name']), + 'name_cases' => $this->getNameCases(str($attrs['name'])), 'documented' => $existingProperties->contains($attrs['name']), 'cast' => $this->getCastReturnType($attrs['cast']) ])) @@ -179,7 +179,7 @@ $models = new class($factory) { /** * @return array */ - protected function getNameCases(\\Illuminate\\Support\\Stringable $name): array + private function getNameCases(\\Illuminate\\Support\\Stringable $name): array { return collect([ $name->camel()->toString(), From 5d869809c03188e534b29c8fa1d5b485a1829601 Mon Sep 17 00:00:00 2001 From: N1ebieski Date: Mon, 14 Apr 2025 21:59:02 +0000 Subject: [PATCH 12/12] refactoring --- src/support/str.ts | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 src/support/str.ts diff --git a/src/support/str.ts b/src/support/str.ts deleted file mode 100644 index 96666afc..00000000 --- a/src/support/str.ts +++ /dev/null @@ -1,12 +0,0 @@ -export const camel = (str: string): string => { - return str - .replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : '') // Removing separators and converting to uppercase - .replace(/^(.)/, (c) => c.toLowerCase()); // First letter lowercase -}; - -export const snake = (str: string): string => { - return str - .replace(/([a-z])([A-Z])/g, '$1_$2') // Separation of camelCase - .replace(/[-\s]+/g, '_') // Converting spaces and dashes to underscores - .toLowerCase(); -}; \ No newline at end of file