Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
7 changes: 4 additions & 3 deletions ftplugin/blade.vim
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ setlocal comments+=s:{{--,m:\ \ \ \ ,e:--}}
if exists('loaded_matchit') && exists('b:match_words')
" Append to html matchit words
let b:match_words .= ',' .
\ '@\%(section\s*([^\,]*)\|if\|unless\|foreach\|forelse\|for\|while\|push\|can\|cannot\|hasSection\|' .
\ 'php\s*(\@!\|verbatim\|component\|slot\|prepend\)' .
\ '@\%(section\s*([^\,]*)\|if\|unless\|for\|while\|push\|can\|hasSection\|' .
\ 'php\s*(\@!\|verbatim\|component\|slot\|prepend\|once\|hasstack\|fragment\|sectionMissing\|' .
\ 'error\|isset\|empty\s*(\|auth\|guest\|env\|production\|session\|context\|switch\)' .
\ ':' .
\ '@\%(else\|elseif\|empty\|break\|continue\|elsecan\|elsecannot\)\>' .
\ '@\%(else\w*\|empty\%(\s*(\)\@!\|break\|continue\|case\|default\)\>' .
\ ':' .
\ '@\%(end\w\+\|stop\|show\|append\|overwrite\)' .
\ ',{:},\[:\],(:)'
Expand Down
29 changes: 24 additions & 5 deletions syntax/blade.vim
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,33 @@ else
setlocal iskeyword+=@-@
endif

syn region bladeEcho matchgroup=bladeDelimiter start="@\@<!{{" end="}}" contains=@bladePhp,bladePhpParenBlock containedin=ALLBUT,@bladeExempt keepend
syn region bladeEcho matchgroup=bladeDelimiter start="@\@1<!{{" end="}}" contains=@bladePhp,bladePhpParenBlock containedin=ALLBUT,@bladeExempt keepend
syn region bladeEcho matchgroup=bladeDelimiter start="{!!" end="!!}" contains=@bladePhp,bladePhpParenBlock containedin=ALLBUT,@bladeExempt keepend
syn region bladeComment matchgroup=bladeDelimiter start="{{--" end="--}}" contains=bladeTodo containedin=ALLBUT,@bladeExempt keepend

syn keyword bladeKeyword @if @elseif @foreach @forelse @for @while @can @cannot @elsecan @elsecannot @include
\ @includeIf @each @inject @extends @section @stack @push @unless @yield @parent @hasSection @break @continue
\ @includeIf @each @inject @extends @section @stack @push @unless @yield @@parent @hasSection @break @continue
\ @unset @lang @choice @component @slot @prepend @json @isset @auth @guest @switch @case @includeFirst @empty
\ @includeWhen @props @livewire @error @production @env @sectionMissing @disabled @readonly @required
\ @includeUnless @once @pushOnce
\ @includeUnless @once @pushOnce @pushIf @elsePushIf @elsePush @prependOnce @hasstack @context @session @use
\ @aware @method @fragment @canany @elsecanany @elseauth @elseguest @bool @vite @dd @dump @js
\ @extendsFirst
\ nextgroup=bladePhpParenBlock skipwhite containedin=ALLBUT,@bladeExempt

syn keyword bladeKeyword @else @endif @endunless @endfor @endforeach @endforelse @endwhile @endcan
\ @endcannot @stop @append @endsection @endpush @show @overwrite @verbatim @endverbatim @endcomponent
\ @endslot @endprepend @endisset @endempty @endauth @endguest @endswitch @enderror @endproduction
\ @endenv @endonce @endPushOnce
\ @endenv @endonce @endPushOnce @endPushIf @endPrependOnce @endcontext @endsession @default @endfragment
\ @csrf @endcanany @viteReactRefresh @endlang
\ containedin=ALLBUT,@bladeExempt

syn keyword bladeAttrParen @class @style @checked @selected @disabled @readonly @required
\ nextgroup=bladePhpParenBlock skipwhite containedin=ALLBUT,@bladeAttrExempt

syn match bladeAttrVar "\<:\$\w\+=\@!" contains=phpIdentifier containedin=ALLBUT,@bladeAttrExempt
syn match bladeAttrString #\<:[A-Za-z0-9-\.]\+=\ze["']# nextgroup=bladePhpQuoteBlock containedin=ALLBUT,@bladeAttrExempt contains=bladeAttrEq
syn match bladeAttrEq '=' contained

if exists('g:blade_custom_directives')
exe "syn keyword bladeKeyword @" . join(g:blade_custom_directives, ' @') . " nextgroup=bladePhpParenBlock skipwhite containedin=ALLBUT,@bladeExempt"
endif
Expand All @@ -54,9 +64,12 @@ syn region bladePhpRegion matchgroup=bladeKeyword start="\<@php\>\s*(\@!" end=
syn match bladeKeyword "@php\ze\s*(" nextgroup=bladePhpParenBlock skipwhite containedin=ALLBUT,@bladeExempt

syn region bladePhpParenBlock matchgroup=bladeDelimiter start="\s*(" end=")" contains=@bladePhp,bladePhpParenBlock skipwhite contained
syn region bladePhpQuoteBlock matchgroup=bladeDelimiter start='\(\<:[A-Za-z0-9-\.]\+=\)\@50<="' end='"' contains=@bladePhp contained
syn region bladePhpQuoteBlock matchgroup=bladeDelimiter start="\(\<:[A-Za-z0-9-\.]\+=\)\@50<='" end="'" contains=@bladePhp contained

syn cluster bladePhp contains=@phpClTop
syn cluster bladeExempt contains=bladeComment,bladePhpRegion,bladePhpParenBlock,@htmlTop
syn cluster bladeExempt contains=bladeComment,bladePhpRegion,bladePhpParenBlock,bladePhpQuoteBlock,@htmlTop
syn cluster bladeAttrExempt contains=bladeComment,bladePhpRegion,bladePhpParenBlock,bladePhpQuoteBlock

syn cluster htmlPreproc add=bladeEcho,bladeComment,bladePhpRegion

Expand All @@ -68,6 +81,12 @@ hi def link bladeComment Comment
hi def link bladeTodo Todo
hi def link bladeKeyword Statement

hi def link bladeAttr PreProc
hi def link bladeAttrParen bladeAttr
hi def link bladeAttrString bladeAttr
hi def link bladeAttrVar bladeAttr
hi def link bladeAttrEq htmlTag

let b:current_syntax = 'blade'

if exists('main_syntax') && main_syntax == 'blade'
Expand Down
155 changes: 148 additions & 7 deletions test.blade.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
@props(['foo' => 'bar', 'baz'])

@aware(['parent' => 'property'])

@extends('parent.name')
@extendsFirst(["page.$id", "page.default"])

@use('App\Models\Example', 'Alias')
@use('App\Models\{Foo, Bar}')
@use(function App\Helpers\format_currency)
@use(const App\Constants\MAX_ATTEMPTS)
@use(function App\Helpers\{format_currency, format_date})
@use(const App\Constants\{MAX_ATTEMPTS, DEFAULT_TIMEOUT})

<?php if($foo='bar' ) { $something() } ?>
Hello, {{ $name }}.

Expand Down Expand Up @@ -37,6 +49,8 @@

@forelse ($users as $user)
<li>{{ $user->name }}</li>
@break ($user->last)
@continue ($user->skip)
@empty
<p>No users</p>
@endforelse
Expand All @@ -59,6 +73,12 @@
<!-- current user cannot publish the post -->
@endcannot

@canany(['update', 'view'], $post)
<!-- current user can either update or view post -->
@elsecanany(['create'], \App\Models\Post::class)
<!-- current user can create a post -->
@endcanany

<div>
@include('shared.errors')
<form>
Expand All @@ -80,9 +100,29 @@

@inject('metrics', 'App\Services\MetricsService')

@push('scripts')
@once
@push('scripts')
<script src="/example.js"></script>
@endpush
@endonce

@pushOnce('scripts')
<script src="/example.js"></script>
@endpush
@endPushOnce

@pushIf($shouldPush, 'scripts')
<script src="/maybe.js"></script>
@elsePushIf($pushOther, 'scripts')
<script src="/other.js"></script>
@elsePush
<script src="/fallback.js"></script>
@endPushIf

@hasstack('list')
<ul>
@stack('list')
</ul>
@endif

<head>
<!-- Head Contents -->
Expand All @@ -95,13 +135,24 @@
</title>

@stack('scripts')

@viteReactRefresh
@vite('resources/js/app.jsx')
</head>

@prepend('scripts')
<script src="{{ mix('/js/manifest.js') }}"></script>
<script src="{{ mix('/js/vendor.js') }}"></script>
@endprepend

@prependOnce('scripts')
<script src="{{ mix('/js/prepend.js') }}"></script>
@endPrependOnce

@fragment('list')
<!-- list contents -->
@endfragment

<div>
@section('sidebar')
This is the master sidebar.
Expand All @@ -113,11 +164,49 @@
@section('title', 'Page Title')

@section('sidebar')
@parent
@@parent
<p>This is appended to the master sidebar.</p>
@endsection

<input name="example" {{ old('example') ? 'checked' : '' }} />
@sectionMissing('navigation')
default navigation
@endif

<form method="POST">
@method('PUT')
@csrf

<input name="example" {{ old('example') ? 'checked' : '' }} />

<input
type="checkbox"
name="terms"
@checked(old('active', $isActive))
class="@error('terms') is-invalid @else is-valid @enderror"
/>
@error('terms')
<div class="alert alert-danger">{{ $message }}</div>
@enderror

<input
type="text"
@class($classes)
@style($styles)
value="initial"
@disabled($disable)
@readonly($user->isNotAdmin())
@required($user->isAdmin())
aria-disabled="@bool($disable)"
/>

<select name="shape">
@foreach ($shapes as $shape)
<option value="{{ $shape }}" @selected(old('shape') == $shape)>
{{ $shape }}
</option>
@endforeach
</select>
</form>

<?php
$collection = collect([
Expand Down Expand Up @@ -165,6 +254,7 @@

@lang('messages.welcome')
@choice('messages.items', 3)
@endlang

@component('app')
@slot('title')
Expand All @@ -174,6 +264,8 @@

@json($foo)

@js($array) {{-- undocumented alternative to {{ Js::from($array) }} --}}

@isset($foo)
records
@endisset
Expand All @@ -183,17 +275,66 @@
@endempty

@auth('admin')
authenticated
authenticated as admin
@elseauth('editor')
authenticated as editor
@endauth

@guest('admin')
not authenticated
not authenticated as admin
@elseguest('editor')
not authenticated as admin or guest
@endguest

@production
only in production
@endproduction

@env(['staging', 'production'])
in staging or production
@endenv

@session('status')
session status is {{ $value }}
@endsession

@context('canonical')
<link href="{{ $value }}" rel="canonical">
@endcontext

@switch($i)
@case(1)
case code
@break

@default
default case

@endswitch

@includeFirst
<a
href="#"
@class([
'p-4',
'active' => $isActive,
'big' => $size > 4, // html tag should not end yet
])
@style([
'margin: 0',
'font-weight: bold' => $isActive,
])
>Link</a>

@includeIf('view.name')
@includeWhen($condition, 'view.name')
@includeUnless($condition, 'view.name')
@includeFirst(['custom.name', 'name'])

<x-profile :$userId :$name />
<x-profile :user-id="$userId ?? null" :name="$name" />

{{-- double colon prefix not treated as php --}}
<x-collapse ::class="{ collapsed: isCollapsed }" />

@dd($foo)
@dump($foo)