Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | 8x 1x 5x 5x 4x 4x 4x 3x 3x 2x 2x 4x 4x 3x | // src/providers/FindUsagesProvider.ts
import * as vscode from 'vscode';
import type { CacheManager } from '../cache/CacheManager';
// ─── Find Usages / References Provider ───────────────────────────────────
/**
* Implements vscode.ReferenceProvider so "Find All References" works for:
* - Route names (all route('name') / to_route('name') calls)
* - View names (all view('name') / @include('name') calls)
* - Translation keys (all __('key') / trans('key') calls)
* - Config keys (all config('key') calls)
* - Eloquent scopes (all ::scope() calls)
* - Gate / policy names
*/
export class LaravelReferenceProvider implements vscode.ReferenceProvider {
constructor(
private readonly cache: CacheManager,
private readonly workspaceRoot: string
) {}
async provideReferences(
document: vscode.TextDocument,
position: vscode.Position,
_context: vscode.ReferenceContext,
_token: vscode.CancellationToken
): Promise<vscode.Location[]> {
const wordRange = document.getWordRangeAtPosition(position, /['"][^'"]+['"]/);
if (!wordRange) { return []; }
const word = document.getText(wordRange).replace(/['"]/g, '');
const index = this.cache.getIndex();
// Determine what kind of symbol this is
let searchPattern: RegExp | null = null;
if (index.routes.has(word)) {
searchPattern = new RegExp(`\\b(?:route|to_route|signedRoute|temporarySignedRoute)\\s*\\(\\s*['"]${escapeRe(word)}['"]`, 'g');
} else if (index.views.has(word)) {
searchPattern = new RegExp(`(?:view|View::make|@include|@extends|@component|Inertia::render)\\s*\\(\\s*['"]${escapeRe(word)}['"]`, 'g');
} else if (Array.from(index.translations.values()).some((t) => t.key === word)) {
searchPattern = new RegExp(`(?:__|trans|\\@lang)\\s*\\(\\s*['"]${escapeRe(word)}['"]`, 'g');
} else if (index.configs.has(word)) {
searchPattern = new RegExp(`\\bconfig\\s*\\(\\s*['"]${escapeRe(word)}['"]`, 'g');
} else {
// Try generic string search for gates/scopes
searchPattern = new RegExp(`['"]${escapeRe(word)}['"]`, 'g');
}
if (!searchPattern) { return []; }
const phpFiles = await vscode.workspace.findFiles(
new vscode.RelativePattern(this.workspaceRoot, '**/*.{php,blade.php}'),
'{vendor/**,node_modules/**}'
);
const locations: vscode.Location[] = [];
for (const fileUri of phpFiles) {
try {
const fileDoc = await vscode.workspace.openTextDocument(fileUri);
const fileText = fileDoc.getText();
let match: RegExpExecArray | null;
searchPattern.lastIndex = 0;
while ((match = searchPattern.exec(fileText)) !== null) {
// Find the position of the string literal within the match
const literalStart = fileText.indexOf(`'${word}'`, match.index) !== -1
? fileText.indexOf(`'${word}'`, match.index)
: fileText.indexOf(`"${word}"`, match.index);
if (literalStart === -1 || literalStart > match.index + match[0].length) { continue; }
const startPos = fileDoc.positionAt(literalStart + 1);
const endPos = fileDoc.positionAt(literalStart + 1 + word.length);
locations.push(new vscode.Location(fileUri, new vscode.Range(startPos, endPos)));
}
} catch { /* skip unreadable files */ }
}
return locations;
}
}
// ─── Eloquent Scope / Relation Find Usages ────────────────────────────────
export class EloquentMemberReferenceProvider implements vscode.ReferenceProvider {
constructor(
_cache: CacheManager,
private readonly workspaceRoot: string
) {}
async provideReferences(
document: vscode.TextDocument,
position: vscode.Position,
_context: vscode.ReferenceContext,
_token: vscode.CancellationToken
): Promise<vscode.Location[]> {
// Get the member name under cursor
const wordRange = document.getWordRangeAtPosition(position, /\b[a-zA-Z_]\w*\b/);
if (!wordRange) { return []; }
const memberName = document.getText(wordRange);
// Check if it's a scope method
const lineText = document.lineAt(position).text;
const isScope = /public\s+function\s+scope/.test(lineText) ||
/public\s+function\s+/.test(lineText);
if (!isScope) { return []; }
// For scopes: search for ::scopeName() calls
const phpFiles = await vscode.workspace.findFiles(
new vscode.RelativePattern(this.workspaceRoot, '**/*.{php,blade.php}'),
'{vendor/**,node_modules/**}'
);
const locations: vscode.Location[] = [];
// scope method name (strip "scope" prefix for usage)
const usageName = memberName.startsWith('scope')
? memberName.charAt(5).toLowerCase() + memberName.slice(6)
: memberName;
const pattern = new RegExp(`::${escapeRe(usageName)}\\s*\\(|->\\s*${escapeRe(usageName)}\\s*\\(`, 'g');
for (const fileUri of phpFiles) {
try {
const fileDoc = await vscode.workspace.openTextDocument(fileUri);
const fileText = fileDoc.getText();
let match: RegExpExecArray | null;
pattern.lastIndex = 0;
while ((match = pattern.exec(fileText)) !== null) {
const nameStart = match.index + match[0].indexOf(usageName);
const startPos = fileDoc.positionAt(nameStart);
const endPos = fileDoc.positionAt(nameStart + usageName.length);
locations.push(new vscode.Location(fileUri, new vscode.Range(startPos, endPos)));
}
} catch { /* skip */ }
}
return locations;
}
}
// ─── View Usages Line Marker (CodeLens) ───────────────────────────────────
export class ViewUsagesCodeLensProvider implements vscode.CodeLensProvider {
constructor(private readonly cache: CacheManager) {}
provideCodeLenses(document: vscode.TextDocument): vscode.CodeLens[] {
if (!document.fileName.endsWith('.blade.php')) { return []; }
const index = this.cache.getIndex();
const viewName = this.fileToViewName(document.uri.fsPath);
if (!viewName) { return []; }
// Check if this view exists in any route action or controller
const isReferenced = Array.from(index.routes.values()).some(
(r) => r.action && r.action.toLowerCase().includes(viewName.replace(/\./g, ''))
);
if (!isReferenced && !index.views.has(viewName)) { return []; }
const lens = new vscode.CodeLens(
new vscode.Range(0, 0, 0, 0),
{
title: `👁 Find view usages`,
command: 'laravel-maestro.findViewUsages',
arguments: [viewName],
}
);
return [lens];
}
private fileToViewName(filePath: string): string | undefined {
const match = /resources[/\\]views[/\\](.+)\.blade\.php$/.exec(filePath);
if (!match) { return undefined; }
return match[1].replace(/[/\\]/g, '.');
}
}
// ─── Helpers ──────────────────────────────────────────────────────────────
function escapeRe(str: string): string {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
|