Skip to content

Commit 6b68de3

Browse files
committed
added blade spacer
1 parent b1efadb commit 6b68de3

File tree

4 files changed

+150
-1
lines changed

4 files changed

+150
-1
lines changed

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,11 @@
427427
"default": true,
428428
"generated": true,
429429
"description": "Enable completion for view."
430+
},
431+
"Laravel.bladeSpacer.enableTwig": {
432+
"type": "boolean",
433+
"default": true,
434+
"description": "Enable support for Twig templating tags (e.g. {% %} or {# #})"
430435
}
431436
}
432437
}

src/blade/bladeSpacer.ts

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import {
2+
SnippetString,
3+
Range,
4+
TextEditor,
5+
TextDocument,
6+
TextDocumentContentChangeEvent,
7+
commands,
8+
TextDocumentChangeEvent
9+
} from 'vscode';
10+
11+
import { config } from '@src/support/config';
12+
13+
const TAG_DOUBLE = 0;
14+
const TAG_UNESCAPED = 1;
15+
const TAG_COMMENT = 2;
16+
const TAG_TWIG_PER = 3;
17+
const TAG_TWIG_HASH = 4;
18+
19+
const charsForChange = (doc: TextDocument, change: TextDocumentContentChangeEvent) => {
20+
if (change.text === '!') {
21+
return 2;
22+
} else if (change.text === '-') {
23+
let start = change.range.start;
24+
let end = change.range.start;
25+
26+
try {
27+
start = start.translate(0, -2);
28+
end = end.translate(0, -1);
29+
} catch (error) {
30+
// VS Code doesn't like negative numbers passed
31+
// to translate (even though it works fine), so
32+
// this block prevents debug console errors
33+
}
34+
35+
let textRange = doc.getText(new Range(start, end));
36+
if (textRange === ' ') {
37+
return 4;
38+
}
39+
return 3;
40+
}
41+
return 1;
42+
};
43+
44+
const spaceReplace = (editor: TextEditor, tagType: number, ranges: Array<Range>) => {
45+
const twigEnabled = config('bladeSpacer.enableTwig', true);
46+
47+
if (tagType === TAG_DOUBLE) {
48+
return editor.insertSnippet(new SnippetString('{{ ${1:${TM_SELECTED_TEXT/[{}]//g}} }}$0'), ranges);
49+
} else if (tagType === TAG_UNESCAPED) {
50+
return editor.insertSnippet(new SnippetString('{!! ${1:${TM_SELECTED_TEXT/[{} !]//g}} !!}$0'), ranges);
51+
} else if (tagType === TAG_COMMENT) {
52+
return editor.insertSnippet(new SnippetString('{{-- ${1:${TM_SELECTED_TEXT/(--)|[{} ]//g}} --}}$0'), ranges);
53+
} else if (twigEnabled && tagType === TAG_TWIG_PER) {
54+
return editor.insertSnippet(new SnippetString('{% $1 %}$0'), ranges);
55+
} else if (twigEnabled && tagType === TAG_TWIG_HASH) {
56+
return editor.insertSnippet(new SnippetString('{# $1 #}$0'), ranges);
57+
}
58+
};
59+
60+
export const bladeSpacer = async (e: TextDocumentChangeEvent, editor?: TextEditor) => {
61+
if (!editor) {
62+
return;
63+
}
64+
65+
const triggers = ['{}', '!', '-', '{', '%', '#'];
66+
const expressions = [
67+
/({{(?!\s|-))(.*?)(}})/,
68+
/({!!(?!\s))(.*?)?(}?)/,
69+
/({{[\s]?--)(.*?)?(}})/,
70+
/({%(?!\s))(.*?)?(}?)/,
71+
/({#(?!\s))(.*?)?(}?)/
72+
];
73+
let tagType: number = -1;
74+
75+
let ranges: Array<Range> = [];
76+
let offsets: Array<number> = [];
77+
78+
// changes (per line) come in right-to-left when we need them left-to-right
79+
const changes = e.contentChanges.slice().reverse();
80+
81+
changes.forEach((change) => {
82+
if (triggers.indexOf(change.text) !== -1) {
83+
if (!offsets[change.range.start.line]) {
84+
offsets[change.range.start.line] = 0;
85+
}
86+
87+
let startOffset = offsets[change.range.start.line] - charsForChange(e.document, change);
88+
let start = change.range.start;
89+
try {
90+
start = start.translate(0, startOffset);
91+
} catch (error) {
92+
// VS Code doesn't like negative numbers passed
93+
// to translate (even though it works fine), so
94+
// this block prevents debug console errors
95+
}
96+
97+
let lineEnd = e.document.lineAt(start.line).range.end;
98+
99+
for (let i = 0; i < expressions.length; i++) {
100+
// if we typed a - or a !, don't consider the "double" tag type
101+
if (i === TAG_DOUBLE && ['-', '!'].indexOf(change.text) !== -1) {
102+
continue;
103+
}
104+
105+
// Only look at unescaped tags if we need to
106+
if (i === TAG_UNESCAPED && change.text !== '!') {
107+
continue;
108+
}
109+
110+
// Only look at unescaped tags if we need to
111+
if (i === TAG_COMMENT && change.text !== '-') {
112+
continue;
113+
}
114+
115+
let tag = expressions[i].exec(e.document.getText(new Range(start, lineEnd)));
116+
117+
if (tag) {
118+
tagType = i;
119+
ranges.push(new Range(start, start.translate(0, tag[0].length)));
120+
offsets[start.line] += tag[1].length;
121+
}
122+
}
123+
}
124+
});
125+
126+
if (ranges.length > 0) {
127+
await spaceReplace(editor, tagType, ranges)?.then(async () => {
128+
try {
129+
await commands.executeCommand('extension.vim_escape');
130+
await commands.executeCommand('extension.vim_right');
131+
await commands.executeCommand('extension.vim_insert');
132+
} catch (error) {
133+
// We don't care if this fails, because it means the user
134+
// does NOT have the VSCodeVim extension installed
135+
}
136+
});
137+
ranges = [];
138+
tagType = -1;
139+
}
140+
};

src/extension.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { setParserBinaryPath } from "./support/parser";
2525
import { clearDefaultPhpCommand, initVendorWatchers } from "./support/php";
2626
import { hasWorkspace, projectPathExists } from "./support/project";
2727
import { cleanUpTemp } from "./support/util";
28+
import { bladeSpacer } from "./blade/bladeSpacer";
2829

2930
let client: LanguageClient;
3031

@@ -92,6 +93,9 @@ export function activate(context: vscode.ExtensionContext) {
9293
vscode.workspace.onDidSaveTextDocument((event) => {
9394
updateDiagnostics(vscode.window.activeTextEditor);
9495
}),
96+
vscode.workspace.onDidChangeTextDocument((event) => {
97+
bladeSpacer(event, vscode.window.activeTextEditor);
98+
}),
9599
// vscode.languages.registerDocumentHighlightProvider(
96100
// documentSelector,
97101
// new DocumentHighlight(),

src/support/generated-config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export type GeneratedConfigKey = 'appBinding.diagnostics' | 'appBinding.hover' | 'appBinding.link' | 'appBinding.completion' | 'asset.diagnostics' | 'asset.hover' | 'asset.link' | 'asset.completion' | 'auth.diagnostics' | 'auth.hover' | 'auth.link' | 'auth.completion' | 'bladeComponent.link' | 'bladeComponent.completion' | 'config.diagnostics' | 'config.hover' | 'config.link' | 'config.completion' | 'controllerAction.diagnostics' | 'controllerAction.hover' | 'controllerAction.link' | 'controllerAction.completion' | 'env.diagnostics' | 'env.hover' | 'env.link' | 'env.completion' | 'inertia.diagnostics' | 'inertia.hover' | 'inertia.link' | 'inertia.completion' | 'livewireComponent.link' | 'livewireComponent.completion' | 'middleware.diagnostics' | 'middleware.hover' | 'middleware.link' | 'middleware.completion' | 'mix.diagnostics' | 'mix.hover' | 'mix.link' | 'mix.completion' | 'paths.link' | 'route.diagnostics' | 'route.hover' | 'route.link' | 'route.completion' | 'translation.diagnostics' | 'translation.hover' | 'translation.link' | 'translation.completion' | 'view.diagnostics' | 'view.hover' | 'view.link' | 'view.completion';
1+
export type GeneratedConfigKey = 'appBinding.diagnostics' | 'appBinding.hover' | 'appBinding.link' | 'appBinding.completion' | 'asset.diagnostics' | 'asset.hover' | 'asset.link' | 'asset.completion' | 'auth.diagnostics' | 'auth.hover' | 'auth.link' | 'auth.completion' | 'bladeComponent.link' | 'bladeComponent.completion' | 'config.diagnostics' | 'config.hover' | 'config.link' | 'config.completion' | 'controllerAction.diagnostics' | 'controllerAction.hover' | 'controllerAction.link' | 'controllerAction.completion' | 'env.diagnostics' | 'env.hover' | 'env.link' | 'env.completion' | 'inertia.diagnostics' | 'inertia.hover' | 'inertia.link' | 'inertia.completion' | 'livewireComponent.link' | 'livewireComponent.completion' | 'middleware.diagnostics' | 'middleware.hover' | 'middleware.link' | 'middleware.completion' | 'mix.diagnostics' | 'mix.hover' | 'mix.link' | 'mix.completion' | 'paths.link' | 'route.diagnostics' | 'route.hover' | 'route.link' | 'route.completion' | 'translation.diagnostics' | 'translation.hover' | 'translation.link' | 'translation.completion' | 'view.diagnostics' | 'view.hover' | 'view.link' | 'view.completion' | 'bladeSpacer.enableTwig';

0 commit comments

Comments
 (0)