Skip to content

Commit

Permalink
feat: Blade Directive Completion (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
yaegassy committed Mar 19, 2022
1 parent 37676a2 commit 87faa93
Show file tree
Hide file tree
Showing 6 changed files with 270 additions and 3 deletions.
80 changes: 80 additions & 0 deletions data/completion/blade-directive.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
{
"@extends": "extends view layout",
"@yield": "yield content section",
"@section": "content section show",
"@endsection": "content section",
"@show": "content section show",
"@include": "include view",
"@if": "$loop->last",
"@endif": "$loop->last",
"@else": "@hasSection condition",
"@hasSection": "@hasSection condition",
"@unless": "@unless block",
"@endunless": "@unless block",
"@for": "@for block",
"@endfor": "@for block",
"@foreach": "@foreach block",
"@endforeach": "@foreach block",
"@forelse": "@forelse block",
"@empty": "empty",
"@endforelse": "@forelse block",
"@while": "@while block",
"@endwhile": "@while block",
"@each": "@each loop",
"@verbatim": "displaying JavaScript variables in a large portion of your template",
"@endverbatim": "displaying JavaScript variables in a large portion of your template",
"@push": "@push stack",
"@endpush": "@push stack",
"@stack": "@stack",
"@inject": "@inject Service",
"@can": "display a portion of the page only if the user is authorized to perform a given action.",
"@endcan": "display a portion of the page only if the user is authorized to perform a given action.",
"@elsecan": "display a portion of the page only if the user is authorized to perform a given action.",
"@canany": "display a portion of the page only if the user is authorized to perform a given action.",
"@endcanany": "display a portion of the page only if the user is authorized to perform a given action.",
"@elsecanany": "display a portion of the page only if the user is authorized to perform a given action.",
"@cannot": "display a portion of the page only if the user is authorized to perform a given action.",
"@endcannot": "display a portion of the page only if the user is authorized to perform a given action.",
"@elsecannot": "display a portion of the page only if the user is authorized to perform a given action.",
"@php": "@php block code in view",
"@endphp": "@php block code in view",
"@includeIf": "include a view that may or may not be present, you should use the @includeIf directive",
"@component": "component",
"@endcomponent": "component",
"@slot": "slot",
"@endslot": "slot",
"@isset": "isset",
"@endisset": "isset",
"@endempty": "empty",
"@error": "error",
"@enderror": "error",
"@includeWhen": "includeWhen",
"@auth": "auth",
"@endauth": "auth",
"@guest": "guest",
"@endguest": "guest",
"@switch": "switch",
"@case": "switch",
"@break": "switch",
"@default": "switch",
"@endswitch": "switch",
"@includeFirst": "includeFirst",
"@csrf": "form csrf field",
"@method": "form method field",
"@dump": "dump",
"@lang": "lang",
"@includeUnless": "includeUnless",
"@props": "Blade component data properties",
"@env": "env",
"@endenv": "env",
"@production": "production",
"@endproduction": "production",
"@once": "define a portion of template that will only be evaluated once per rendering cycle",
"@endonce": "define a portion of template that will only be evaluated once per rendering cycle",
"@aware": "Accessing data from a parent component (Laravel 8.64)",
"@js": "This directive is useful to properly escape JSON within HTML quotes",
"@class": "conditionally compiles a CSS class string. (Laravel 8.51)",
"@checked": "This directive will echo checked if the provided condition evaluates to true (Laravel 9.x)",
"@selected": "The @selected directive may be used to indicate if a given select option should be \"selected\" (Laravel 9.x)",
"@disabled": "The @disabled directive may be used to indicate if a given element should be \"disabled\" (Laravel 9.x)"
}
5 changes: 5 additions & 0 deletions data/completion/livewire-directive.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"@livewireStyles": "Livewire Styles directive",
"@livewireScripts": "Livewire Scripts directive",
"@livewire": "Livewire nesting components"
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
"snippets:helper": "curl -o data/snippets/helpers.json https://github.com/raw/onecentlin/laravel-blade-snippets-vscode/master/snippets/helpers.json",
"snippets:livewire": "curl -o data/snippets/livewire.json https://github.com/raw/onecentlin/laravel-blade-snippets-vscode/master/snippets/livewire.json",
"snippets:snippets": "curl -o data/snippets/snippets.json https://github.com/raw/onecentlin/laravel-blade-snippets-vscode/master/snippets/snippets.json",
"data:format": "prettier --write data/snippets/*.json",
"generate": "node scripts/snippetsToCompletionJson.js && yarn data:format",
"data:format": "prettier --write data/snippets/*.json && prettier --write data/completion/*.json",
"lint": "eslint src --ext ts",
"clean": "rimraf lib",
"watch": "node esbuild.js --watch",
Expand Down
49 changes: 49 additions & 0 deletions scripts/snippetsToCompletionJson.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
const fs = require('fs');
const path = require('path');

const generate = (filename, outputPath) => {
fs.readFile(filename, 'utf8', (err, data) => {
if (err) {
console.error(err);
return;
}

jsonData = JSON.parse(data);

const output = {};
for (const key of Object.keys(jsonData)) {
let body = '';
if (typeof jsonData[key]['body'] === 'string' || jsonData[key]['body'] instanceof String) {
body = jsonData[key]['body'];
} else if (Array.isArray(jsonData[key]['body'])) {
body = jsonData[key]['body'].join('');
}

if (body) {
matches = body.match(/@.[a-zA-Z_0-9]*/g);
if (matches) {
matches.forEach((v) => {
if (v !== '@{') {
output[v] = jsonData[key]['description'];
}
});
}
}
}

if (!fs.existsSync(outputPath)) {
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
}

fs.writeFileSync(outputPath, JSON.stringify(output, null, 2));
});
};

generate(
path.join(path.dirname(__dirname), 'data', 'snippets', 'snippets.json'),
path.join(path.dirname(__dirname), 'data', 'completion', 'blade-directive.json')
);
generate(
path.join(path.dirname(__dirname), 'data', 'snippets', 'livewire.json'),
path.join(path.dirname(__dirname), 'data', 'completion', 'livewire-directive.json')
);
95 changes: 95 additions & 0 deletions src/completion/provider/bladeDirective.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import {
CancellationToken,
CompletionContext,
CompletionItem,
CompletionItemKind,
CompletionItemProvider,
CompletionList,
ExtensionContext,
LinesTextDocument,
Position,
workspace,
} from 'coc.nvim';

import path from 'path';
import fs from 'fs';

type CompletionJsonType = {
[key: string]: string;
};

export class BladeDirectiveCompletionProvider implements CompletionItemProvider {
private _context: ExtensionContext;
private directiveJsonPaths: string[];

constructor(context: ExtensionContext) {
this._context = context;
this.directiveJsonPaths = [
path.join(this._context.extensionPath, 'data', 'completion', 'blade-directive.json'),
path.join(this._context.extensionPath, 'data', 'completion', 'livewire-directive.json'),
];
}

async getCompletionItems(completionDataPath: string) {
const completionList: CompletionItem[] = [];
if (fs.existsSync(completionDataPath)) {
const completionJsonText = fs.readFileSync(completionDataPath, 'utf8');
const completionJson: CompletionJsonType = JSON.parse(completionJsonText);
if (completionJson) {
Object.keys(completionJson).map((key) => {
const docDataPath = path.join(
this._context.extensionPath,
'data',
'documantation',
'blade',
key.replace('@', '') + '.md'
);

let documentationText: string | undefined;
try {
documentationText = fs.readFileSync(docDataPath, 'utf8');
} catch (e) {
// noop
documentationText = undefined;
}

completionList.push({
label: key,
kind: CompletionItemKind.Text,
insertText: key.replace('@', ''),
detail: completionJson[key],
documentation: documentationText,
});
});
}
}

return completionList;
}

async provideCompletionItems(
//document: TextDocument,
document: LinesTextDocument,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
position: Position,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
token: CancellationToken,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
context: CompletionContext
): Promise<CompletionItem[] | CompletionList> {
const doc = workspace.getDocument(document.uri);
if (!doc) return [];
const wordRange = doc.getWordRangeAtPosition(Position.create(position.line, position.character - 1), '@');
if (!wordRange) return [];
const text = document.getText(wordRange) || '';
if (!text) return [];

if (!text.startsWith('@')) return [];

const completionItemList: CompletionItem[] = [];
this.directiveJsonPaths.forEach((v) => {
this.getCompletionItems(v).then((vv) => completionItemList.push(...vv));
});
return completionItemList;
}
}
41 changes: 39 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import path from 'path';

import { BladeHoverProvider } from './hover/hover';
import { BladeSnippetsCompletionProvider } from './completion/provider/bladeSnippets';
import { BladeDirectiveCompletionProvider } from './completion/provider/bladeDirective';
import { BladelinterLintEngine } from './lint';
import BladeFormattingEditProvider, { doFormat, fullDocumentRange } from './format';
import BladeDefinitionProvider from './definition';
Expand Down Expand Up @@ -119,10 +120,36 @@ export async function activate(context: ExtensionContext): Promise<void> {
const isEnableCompletion = extConfig.get<boolean>('completion.enable', true);
if (isEnableCompletion) {
const { document } = await workspace.getCurrentState();
const indentexpr = await (await workspace.nvim.buffer).getOption('indentexpr');
if (document.languageId === 'blade') {
try {
await workspace.nvim.command('setlocal iskeyword+=:');
await workspace.nvim.command('setlocal iskeyword+=-');
workspace.registerAutocmd({
event: 'FileType',
pattern: 'blade',
request: true,
callback: async () => {
await workspace.nvim.command('setlocal iskeyword+=:');
await workspace.nvim.command('setlocal iskeyword+=-');
},
});

workspace.registerAutocmd({
event: 'InsertEnter',
pattern: '*.blade.php',
request: true,
callback: async () => {
await workspace.nvim.command('setlocal indentexpr=');
},
});

workspace.registerAutocmd({
event: 'InsertLeave',
pattern: '*.blade.php',
request: true,
callback: async () => {
await workspace.nvim.command(`setlocal indentexpr=${indentexpr}`);
},
});
} catch {
// noop
}
Expand All @@ -136,6 +163,16 @@ export async function activate(context: ExtensionContext): Promise<void> {
new BladeSnippetsCompletionProvider(context, outputChannel)
)
);

context.subscriptions.push(
languages.registerCompletionItemProvider(
'blade-directive',
'blade',
['blade'],
new BladeDirectiveCompletionProvider(context),
['@']
)
);
}

//
Expand Down

0 comments on commit 87faa93

Please sign in to comment.