Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions php-templates/models.php
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,12 @@ protected function getInfo($className)
->toArray();

$data['scopes'] = collect($reflection->getMethods())
->filter(fn($method) =>!$method->isStatic() && ($method->getAttributes(\Illuminate\Database\Eloquent\Attributes\Scope::class) || ($method->isPublic() && str_starts_with($method->name, 'scope'))))
->map(fn($method) => str($method->name)->replace('scope', '')->lcfirst()->toString())
->filter(fn(\ReflectionMethod $method) =>!$method->isStatic() && ($method->getAttributes(\Illuminate\Database\Eloquent\Attributes\Scope::class) || ($method->isPublic() && str_starts_with($method->name, 'scope'))))
->map(fn(\ReflectionMethod $method) => [
"name" => str($method->name)->replace('scope', '')->lcfirst()->toString(),
"uri" => $method->getFileName(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"uri" => $method->getFileName(),
"uri" => $method->getFileName(),
"relative_path" => str($method->getFileName())->after(base_path())->toString(),

In Docker-based environments, $method->getFileName() is the container path, which fails when clicked on hover. Adding relative_path will make it easy to resolve the path to the file in the host

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. I've replaced uri $method->getFileName() with LaravelVsCode::relativePath. Could you check if the problem still occurs?

"start_line" => $method->getStartLine()
])
->values()
->toArray();

Expand Down
5 changes: 5 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
wrapSelectionCommand,
wrapWithHelperCommands,
} from "./commands/wrapWithHelper";
import { ScopeHoverProvider } from "./features/model";
import { configAffected } from "./support/config";
import { collectDebugInfo } from "./support/debug";
import {
Expand Down Expand Up @@ -210,6 +211,10 @@ export async function activate(context: vscode.ExtensionContext) {
...hoverProviders.map((provider) =>
vscode.languages.registerHoverProvider(LANGUAGES, provider),
),
vscode.languages.registerHoverProvider(
PHP_LANGUAGE,
new ScopeHoverProvider(),
),
// ...testRunnerCommands,
// testController,
vscode.languages.registerCodeActionsProvider(
Expand Down
84 changes: 84 additions & 0 deletions src/features/model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { HoverActions } from "@src/hoverAction/support";
import { getModelByClassname } from "@src/repositories/models";
import { detect } from "@src/support/parser";
import { AutocompleteParsingResult } from "@src/types";
import * as vscode from "vscode";

const isInHoverRange = (
range: vscode.Range,
method: AutocompleteParsingResult.MethodCall,
): boolean => {
if (!method.start || !method.end) {
return false;
}

const nodeRange = new vscode.Range(
new vscode.Position(method.start.line, method.start.column),
new vscode.Position(method.end.line, method.end.column),
);

// vs-code-php-parser-cli returns us the position of the entire node, for example:
//
// Example::popular()
// ->traitScope()
// ->active();
//
// so we don't have to check equality of the start and end positions.
// It's enough to check if the node range contains the scope range
return nodeRange.contains(range);
};

export class ScopeHoverProvider implements vscode.HoverProvider {
provideHover(
document: vscode.TextDocument,
position: vscode.Position,
): vscode.ProviderResult<vscode.Hover> {
const range = document.getWordRangeAtPosition(position);

if (!range) {
return null;
}

const scopeName = document.getText(range);

return detect(document).then((results) => {
if (!results) {
return null;
}

const result = results
.filter((result) => result.type === "methodCall")
.find(
(result) =>
result.methodName === scopeName &&
isInHoverRange(range, result),
);

if (!result?.className) {
return null;
}

const scope = getModelByClassname(result.className)?.scopes?.find(
(scope) => scope.name === scopeName,
);

if (!scope?.uri) {
return null;
}

const hoverActions = new HoverActions([
{
title: "Go to implementation",
command: "laravel.open",
arguments: [
vscode.Uri.file(scope.uri),
scope.start_line,
0,
],
},
]);

return new vscode.Hover(hoverActions.getAsMarkdownString(), range);
});
}
}
30 changes: 30 additions & 0 deletions src/hoverAction/support.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import * as vscode from "vscode";

export class HoverActions {
private readonly commands: vscode.Command[];

constructor(commands: vscode.Command[] = []) {
this.commands = commands;
}

public push(command: vscode.Command): this {
this.commands.push(command);

return this;
}

public getAsMarkdownString(): vscode.MarkdownString {
let string = "";

this.commands.forEach((command) => {
string += `<small>[${command.title}](command:${command.command}?${encodeURIComponent(JSON.stringify(command.arguments))})</small>`;
string += `<span style="display: none;">&nbsp;&nbsp;&nbsp;</span>`;
});

const markdownString = new vscode.MarkdownString(string);
markdownString.supportHtml = true;
markdownString.isTrusted = true;

return markdownString;
}
}
9 changes: 8 additions & 1 deletion src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,23 @@ declare namespace Eloquent {
[key: string]: Model;
}

interface Scope {
name: string;
uri: string | false;
start_line: number | false;
}

interface Model {
class: string;
uri: string | false;
database: string;
table: string;
policy: string | null;
attributes: Attribute[];
relations: Relation[];
events: Event[];
observers: Observer[];
scopes: string[];
scopes: Scope[];
extends: string | null;
}

Expand Down
6 changes: 6 additions & 0 deletions src/repositories/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ const load = () => {
});
};

export const getModelByClassname = (
className: string,
): Eloquent.Model | undefined => {
return getModels().items[className];
};

export const getModels = repository<Eloquent.Models>({
load,
pattern: modelPaths
Expand Down
17 changes: 10 additions & 7 deletions src/support/docblocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,16 @@ const getBlocks = (
return model.attributes
.map((attr) => getAttributeBlocks(attr, className))
.concat(
[...model.scopes, "newModelQuery", "newQuery", "query"].map(
(method) => {
return `@method static ${modelBuilderType(
className,
)} ${method}()`;
},
),
[
...model.scopes.map((scope) => scope.name),
"newModelQuery",
"newQuery",
"query",
].map((method) => {
return `@method static ${modelBuilderType(
className,
)} ${method}()`;
}),
)
.concat(model.relations.map((relation) => getRelationBlocks(relation)))
.flat()
Expand Down
8 changes: 6 additions & 2 deletions src/templates/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,12 @@ $models = new class($factory) {
->toArray();

$data['scopes'] = collect($reflection->getMethods())
->filter(fn($method) =>!$method->isStatic() && ($method->getAttributes(\\Illuminate\\Database\\Eloquent\\Attributes\\Scope::class) || ($method->isPublic() && str_starts_with($method->name, 'scope'))))
->map(fn($method) => str($method->name)->replace('scope', '')->lcfirst()->toString())
->filter(fn(\\ReflectionMethod $method) =>!$method->isStatic() && ($method->getAttributes(\\Illuminate\\Database\\Eloquent\\Attributes\\Scope::class) || ($method->isPublic() && str_starts_with($method->name, 'scope'))))
->map(fn(\\ReflectionMethod $method) => [
"name" => str($method->name)->replace('scope', '')->lcfirst()->toString(),
"uri" => $method->getFileName(),
"start_line" => $method->getStartLine()
])
->values()
->toArray();

Expand Down
8 changes: 8 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@ export namespace AutocompleteParsingResult {
methodName: string | null;
className: string | null;
arguments: Arguments;
start?: {
line: number;
column: number;
};
end?: {
line: number;
column: number;
};
}

export interface MethodDefinition {
Expand Down