Skip to content
Merged
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
4 changes: 3 additions & 1 deletion src/Highlighter.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
use Tempest\Highlight\Languages\Text\TextLanguage;
use Tempest\Highlight\Languages\Twig\TwigLanguage;
use Tempest\Highlight\Languages\TypeScript\TypeScriptLanguage;
use Tempest\Highlight\Languages\Vue\VueLanguage;
use Tempest\Highlight\Languages\Xml\XmlLanguage;
use Tempest\Highlight\Languages\Yaml\YamlLanguage;
use Tempest\Highlight\Themes\CssTheme;
Expand Down Expand Up @@ -88,7 +89,8 @@ public function __construct(
->addLanguage(new DotEnvLanguage())
->addLanguage(new IniLanguage())
->addLanguage(new TwigLanguage())
->addLanguage(new SvelteLanguage());
->addLanguage(new SvelteLanguage())
->addLanguage(new VueLanguage());

$this->parseTokens = new ParseTokens();
$this->groupTokens = new GroupTokens();
Expand Down
28 changes: 28 additions & 0 deletions src/Languages/Vue/Injections/VueInterpolationInjection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Tempest\Highlight\Languages\Vue\Injections;

use Tempest\Highlight\Highlighter;
use Tempest\Highlight\Injection;
use Tempest\Highlight\IsInjection;
use Tempest\Highlight\PatternTest;

#[PatternTest(input: '{{ name }}', output: ' name ')]
#[PatternTest(input: '{{ user.id }}', output: ' user.id ')]
#[PatternTest(input: '{{ count + 1 }}', output: ' count + 1 ')]
final class VueInterpolationInjection implements Injection
{
use IsInjection;

public function getPattern(): string
{
return '\{\{(?<match>[\s\S]*?)\}\}';
}

public function parseContent(string $content, Highlighter $highlighter): string
{
return $highlighter->parse($content, 'typescript');
}
}
24 changes: 24 additions & 0 deletions src/Languages/Vue/Injections/VueScriptSetupInjection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Tempest\Highlight\Languages\Vue\Injections;

use Tempest\Highlight\Highlighter;
use Tempest\Highlight\Injection;
use Tempest\Highlight\IsInjection;

final class VueScriptSetupInjection implements Injection
{
use IsInjection;

public function getPattern(): string
{
return '<script\s+setup\s*>(?<match>[\s\S]*?)<\/script>';
}

public function parseContent(string $content, Highlighter $highlighter): string
{
return $highlighter->parse($content, 'javascript');
}
}
24 changes: 24 additions & 0 deletions src/Languages/Vue/Injections/VueStyleScopedInjection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Tempest\Highlight\Languages\Vue\Injections;

use Tempest\Highlight\Highlighter;
use Tempest\Highlight\Injection;
use Tempest\Highlight\IsInjection;

final class VueStyleScopedInjection implements Injection
{
use IsInjection;

public function getPattern(): string
{
return '<style\s+[^>]*\bscoped\b[^>]*>(?<match>[\s\S]*?)<\/style>';
}

public function parseContent(string $content, Highlighter $highlighter): string
{
return $highlighter->parse($content, 'css');
}
}
24 changes: 24 additions & 0 deletions src/Languages/Vue/Injections/VueStyleScssInjection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Tempest\Highlight\Languages\Vue\Injections;

use Tempest\Highlight\Highlighter;
use Tempest\Highlight\Injection;
use Tempest\Highlight\IsInjection;

final class VueStyleScssInjection implements Injection
{
use IsInjection;

public function getPattern(): string
{
return '<style[^>]*\blang="s[ac]ss"[^>]*>(?<match>[\s\S]*?)<\/style>';
}

public function parseContent(string $content, Highlighter $highlighter): string
{
return $highlighter->parse($content, 'scss');
}
}
24 changes: 24 additions & 0 deletions src/Languages/Vue/Injections/VueTypeScriptInjection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Tempest\Highlight\Languages\Vue\Injections;

use Tempest\Highlight\Highlighter;
use Tempest\Highlight\Injection;
use Tempest\Highlight\IsInjection;

final class VueTypeScriptInjection implements Injection
{
use IsInjection;

public function getPattern(): string
{
return '<script[^>]*\blang="ts"[^>]*>(?<match>[\s\S]*?)<\/script>';
}

public function parseContent(string $content, Highlighter $highlighter): string
{
return $highlighter->parse($content, 'typescript');
}
}
24 changes: 24 additions & 0 deletions src/Languages/Vue/Patterns/VueDirectiveArgumentPattern.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Tempest\Highlight\Languages\Vue\Patterns;

use Tempest\Highlight\IsPattern;
use Tempest\Highlight\Pattern;
use Tempest\Highlight\Tokens\TokenTypeEnum;

final readonly class VueDirectiveArgumentPattern implements Pattern
{
use IsPattern;

public function getPattern(): string
{
return '\sv-(?:bind|on|slot|model):(?<match>[\w-]+)';
}

public function getTokenType(): TokenTypeEnum
{
return TokenTypeEnum::PROPERTY;
}
}
24 changes: 24 additions & 0 deletions src/Languages/Vue/Patterns/VueDirectivePattern.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Tempest\Highlight\Languages\Vue\Patterns;

use Tempest\Highlight\IsPattern;
use Tempest\Highlight\Pattern;
use Tempest\Highlight\Tokens\TokenTypeEnum;

final readonly class VueDirectivePattern implements Pattern
{
use IsPattern;

public function getPattern(): string
{
return '(?<=\s)(?<match>v-(?:if|else-if|else|for|show|model|bind|on|html|text|pre|cloak|once|slot|memo))\b';
}

public function getTokenType(): TokenTypeEnum
{
return TokenTypeEnum::PROPERTY;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Tempest\Highlight\Languages\Vue\Patterns;

use Tempest\Highlight\IsPattern;
use Tempest\Highlight\Pattern;
use Tempest\Highlight\Tokens\TokenTypeEnum;

final readonly class VueDirectiveShorthandArgumentPattern implements Pattern
{
use IsPattern;

public function getPattern(): string
{
return '\s[:@#](?<match>[\w-]+)';
}

public function getTokenType(): TokenTypeEnum
{
return TokenTypeEnum::PROPERTY;
}
}
24 changes: 24 additions & 0 deletions src/Languages/Vue/Patterns/VueDirectiveShorthandPattern.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Tempest\Highlight\Languages\Vue\Patterns;

use Tempest\Highlight\IsPattern;
use Tempest\Highlight\Pattern;
use Tempest\Highlight\Tokens\TokenTypeEnum;

final readonly class VueDirectiveShorthandPattern implements Pattern
{
use IsPattern;

public function getPattern(): string
{
return '(?<=\s)(?<match>[:@#])[\w-]+';
}

public function getTokenType(): TokenTypeEnum
{
return TokenTypeEnum::PROPERTY;
}
}
51 changes: 51 additions & 0 deletions src/Languages/Vue/VueLanguage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace Tempest\Highlight\Languages\Vue;

use Override;
use Tempest\Highlight\Languages\Html\HtmlLanguage;
use Tempest\Highlight\Languages\Vue\Injections\VueInterpolationInjection;
use Tempest\Highlight\Languages\Vue\Injections\VueScriptSetupInjection;
use Tempest\Highlight\Languages\Vue\Injections\VueStyleScopedInjection;
use Tempest\Highlight\Languages\Vue\Injections\VueStyleScssInjection;
use Tempest\Highlight\Languages\Vue\Injections\VueTypeScriptInjection;
use Tempest\Highlight\Languages\Vue\Patterns\VueDirectiveArgumentPattern;
use Tempest\Highlight\Languages\Vue\Patterns\VueDirectivePattern;
use Tempest\Highlight\Languages\Vue\Patterns\VueDirectiveShorthandArgumentPattern;
use Tempest\Highlight\Languages\Vue\Patterns\VueDirectiveShorthandPattern;

class VueLanguage extends HtmlLanguage
{
#[Override]
public function getName(): string
{
return 'vue';
}

#[Override]
public function getInjections(): array
{
return [
...parent::getInjections(),
new VueTypeScriptInjection(),
new VueScriptSetupInjection(),
new VueStyleScssInjection(),
new VueStyleScopedInjection(),
new VueInterpolationInjection(),
];
}

#[Override]
public function getPatterns(): array
{
return [
...parent::getPatterns(),
new VueDirectivePattern(),
new VueDirectiveArgumentPattern(),
new VueDirectiveShorthandPattern(),
new VueDirectiveShorthandArgumentPattern(),
];
}
}
79 changes: 79 additions & 0 deletions tests/Bench/Fixtures/vue.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<!-- A Vue 3 SFC counter-and-list component -->
<script setup lang="ts">
import { ref, computed, watch } from 'vue'

type Todo = { id: number; text: string; done: boolean }

const props = defineProps<{
title: string
initial?: Todo[]
}>()

const emit = defineEmits<{
update: [todos: Todo[]]
}>()

const draft = ref('')
const todos = ref<Todo[]>(props.initial ?? [])
const filter = ref<'all' | 'active' | 'done'>('all')

const visible = computed(() => {
switch (filter.value) {
case 'active': return todos.value.filter((t) => !t.done)
case 'done': return todos.value.filter((t) => t.done)
default: return todos.value
}
})

const remaining = computed(() => todos.value.filter((t) => !t.done).length)

watch(todos, (next) => {
emit('update', next)
}, { deep: true })

function add() {
if (!draft.value.trim()) return
todos.value.push({ id: Date.now(), text: draft.value, done: false })
draft.value = ''
}
</script>

<template>
<h1>{{ title }}</h1>

<form @submit.prevent="add">
<input v-model="draft" placeholder="What needs doing?" />
<button type="submit">Add</button>
</form>

<select v-model="filter">
<option value="all">All</option>
<option value="active">Active</option>
<option value="done">Done</option>
</select>

<ul>
<li v-for="todo in visible" :key="todo.id" :class="{ done: todo.done }">
<input type="checkbox" v-model="todo.done" />
{{ todo.text }}
</li>
</ul>

<p v-if="filter === 'all'">Showing everything.</p>
<p v-else-if="filter === 'active'">Showing active todos.</p>
<p v-else>Showing {{ remaining }} remaining.</p>
</template>

<style scoped lang="scss">
h1 {
color: red;

&:hover {
color: blue;
}
}

.done {
text-decoration: line-through;
}
</style>
1 change: 1 addition & 0 deletions tests/Bench/HighlighterBench.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ final class HighlighterBench
'terraform' => 'terraform.txt',
'typescript' => 'typescript.txt',
'twig' => 'twig.txt',
'vue' => 'vue.txt',
'xml' => 'xml.txt',
'yaml' => 'yaml.txt',
];
Expand Down
Loading
Loading