This commit is contained in:
asep komarudin 2023-01-15 00:19:37 +07:00
parent e53a643e3d
commit 1ab37bd478
209 changed files with 79957 additions and 0 deletions

View file

@ -0,0 +1,13 @@
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.json]
indent_style = space
indent_size = 2

6
my-snippets/laravel-blade/.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
out
node_modules
*.vsix
**/*.log
.vscode/tests/
**/temp/

View file

@ -0,0 +1,19 @@
// A launch configuration that launches the extension inside a new window
{
"version": "1.0.0",
"configurations": [
{
"name": "Launch Extension",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceRoot}"
],
"outFiles": [
"${workspaceRoot}/out/**/*.js"
],
"preLaunchTask": "npm"
}
]
}

View file

@ -0,0 +1,4 @@
{
"emmet.triggerExpansionOnTab": true,
"blade.format.enable": true
}

View file

@ -0,0 +1,15 @@
{
"version": "2.0.0",
"type": "shell",
"command": "npm",
// run the custom script "compile" as defined in package.json
"args": [
"run",
"compile",
"--loglevel",
"silent"
],
"isBackground": true,
// The tsc compiler is started in watching mode
"problemMatcher": "$tsc-watch"
}

View file

@ -0,0 +1,18 @@
.vscode/**/*
.gitignore
tests/**/*
.vscode-test/**
out/test/**
test/**
src/**
**/*.map
tsconfig.json
vsc-extension-quickstart.md
server/src/**
server/lib/**
server/*.json
server/node_modules/.bin/
webpack.config.js
node_modules/**
**/*.ts
**/tsconfig.json

View file

@ -0,0 +1,253 @@
## 1.32.0
- Add `@disabled` directive and `b:disabled` snippet ([@JustinByrne](https://github.com/JustinByrne) - [PR #151](https://github.com/onecentlin/laravel-blade-snippets-vscode/pull/151))
- Add `b:class` snippet (PR #136 and PR #140 - Thanks to [@lakuapik](https://github.com/lakuapik) and [@wilsenhc](https://github.com/wilsenhc))
## 1.31.0
- Add `b:aware` and `b:js` snippet
- Add `@aware` directive ([Laravel 8.64](https://laravel-news.com/laravel-8-64-0))
- Add `@js` directive ([Laravel 8.71](https://laravel-news.com/laravel-8-71-0))
- Update `Blade::render` and `Blade::renderComponent` snippet
## 1.30.0
Add Laravel 9 features
- Add `b:checked` and `b:selected` snippet
- Add `@checked` and `@selected` directive syntax highlight
- Add `Blade::render` and `Blade::renderComponent` snippet
## 1.29.0
Happy New Year 2022!
- Add `b:canany` and `b:canany-cananyelse` snippet ([@JustinByrne](https://github.com/JustinByrne) - [PR #144](https://github.com/onecentlin/laravel-blade-snippets-vscode/pull/144))
- Fix snippet
- Update blade syntaxes
- Update packages
## 1.28.0
- Added support attribute expressions syntax highlighting ([@cpof-tea](https://github.com/cpof-tea) - [PR #138](https://github.com/onecentlin/laravel-blade-snippets-vscode/pull/138))
## 1.27.0
- Add `@class` directive syntax highlight
- Update blade syntaxes
- Fix snippet
## 1.26.0
- Add `b:once` snippet ([@lakuapik](https://github.com/lakuapik) - [PR #137](https://github.com/onecentlin/laravel-blade-snippets-vscode/pull/137))
- Add `Blade::stringable` snippet ([@lakuapik](https://github.com/lakuapik) - [PR #135](https://github.com/onecentlin/laravel-blade-snippets-vscode/pull/135))
- Update packages
## 1.25.0
- Add `@once` directive
- Fix ([#121](https://github.com/onecentlin/laravel-blade-snippets-vscode/issues/121) @php() highlighting
- Update blade syntaxes
## 1.24.0
- Update blade syntaxes
## 1.23.0
- Add `@livewireStyles`, `@livewireScripts`, `@livewire` directive (v8.x)
- Add `livewire:styles`, `livewire:scripts`, `livewire:component` snippets
- Cleanup snippets
## 1.22.0
- Add `@includeUnless` directive (v6.x)
- Add environment directives: `@production`, `@env` (v7.x)
- Rename language mode using `Blade` instead of `Laravel Blade`
- Enable language feature in blade language mode
- Reduce extension package size
## 1.21.0
- Add `b:error` snippets ([@CaddyDz](https://github.com/CaddyDz) - [PR #95](https://github.com/onecentlin/laravel-blade-snippets-vscode/pull/95))
- Add `b:props` snippets
- Add blade extensions snippets
- `Blade::component`
- `Blade::include`
- `Blade::if`
- `Blade::directive`
## 1.20.0
- Update blade formatter fixed for updated languageservice
## 1.19.0
- Append html format options to html formatter ([@ayatkyo](https://github.com/ayatkyo) - [PR #87](https://github.com/onecentlin/laravel-blade-snippets-vscode/pull/87))
- Update package dependencies
## 1.18.0
- Add `b:csrf`, `b:method`, `b:dump` snipptes ([@HasanAlyazidi](https://github.com/HasanAlyazidi) - [PR #60](https://github.com/onecentlin/laravel-blade-snippets-vscode/pull/60))
- Fix comment with extra spaces (#59)
- Fix formatting issue in url syntax (#57)
- Fix shorthand `@php()` for Roots/Sage WordPress Template with html tag syntax highlight (#53)
## 1.17.0
- Syntax highlighting enhancement
- Add syntax highlighting for class static method
- Add `b:lang` snippet (#52)
## 1.16.0
- Fix tag attributes completition (#24)
- Fix comment issue in `script`, `style`, `php` block with `Ctrl + /` or `⌘ + /` keymap shortcut (#25, #34)
## 1.15.0
- Support Envoy directives: `@setup`, `@servers`, `@task`, `@story`, `@finished`, `@slack` (#41)
## 1.14.2
- Fix error in Blade Language Server (#46)
- Fix extensionPath of undefined (#47)
- Emmet setting changed (#48)
> Settings below for blade is no longer needed.
>```json
>"emmet.includeLanguages": {
> "blade": "html"
>},
>```
## 1.14.0
- Fix blade syntax broken with VSCode 1.20.0 release (#42)
- Modify the highlight, add to the style and script autocomplete ([@tiansin](https://github.com/tiansin) - [PR #43](https://github.com/onecentlin/laravel-blade-snippets-vscode/pull/43))
- Fix javascript autocompletion not working in script tag (#39)
- Add `b:unless` snippet
## 1.13.0
- Fix spaces on format (#40)
- Enable format selection (#10)
- Enhance blade format (#32, #36)
## 1.12.0
- Add `blade.format.enable` configuration setting for manual enable blade file format. (#30)
```json
"blade.format.enable": true,
```
- Add `@includeFirst` directive
- Add `b:includeFirst` snippet
- Fix minor syntax issue
## 1.11.0
- Fix indent issue #9, #35 ([@izcream](https://github.com/izcream) - [PR #38](https://github.com/onecentlin/laravel-blade-snippets-vscode/pull/38))
- Fix minor whitespace inconsistencies ([@raniesantos](https://github.com/raniesantos) - [PR #28](https://github.com/onecentlin/laravel-blade-snippets-vscode/pull/28/files))
## 1.10.0
- Update syntax highlighting
- Added `Document Highlight Provider` and `Document Format Provider` ([@TheColorRed](https://github.com/TheColorRed) - [PR #17](https://github.com/onecentlin/laravel-blade-snippets-vscode/pull/17))
## 1.9.0
Laravel 5.4 blade directives & snippets:
- Add `@isset`, `@empty`, `@includeWhen` directives
- Add `b:isset`, `b:empty`, `b:includeWhen` snippets
Laravel 5.5 blade directives & snippets:
- Add `@auth`, `@guest`, `@switch`, `@case`, `@default` directives
- Add `b:auth`, `b:guest`, `b:switch` snippets
Syntax Enhancement
- Change grammar of blade directive ([@mikebronner](https://github.com/mikebronner) - [PR #23](https://github.com/onecentlin/laravel-blade-snippets-vscode/pull/23))
## 1.8.2
- Update README ([#18](https://github.com/onecentlin/laravel-blade-snippets-vscode/issues/18), [#19](https://github.com/onecentlin/laravel-blade-snippets-vscode/pull/19))
## 1.8.1
- Fix syntax parse failed [#5](https://github.com/onecentlin/laravel-blade-snippets-vscode/issues/5)
## 1.8.0
- Add `@can` and `@cannot` related directives ([#4](https://github.com/onecentlin/laravel-blade-snippets-vscode/issues/4))
- Add `b:can`, `b:can-elsecan`, `b:cannot`, `b:cannot-elsecannot` authorizing snippets ([#4](https://github.com/onecentlin/laravel-blade-snippets-vscode/issues/4))
- Add `lv:mix` helper
- Fix for loop snippet
## 1.7.0
- Enhance blade syntax highlighting
- Fix loop snippets
## 1.6.1
- Fix extra slashes in `lv:*` helper snippets
## 1.6
- Support `@component` and `@slot` directive added in Laravel 5.4
- Fix [#3](https://github.com/onecentlin/laravel-blade-snippets-vscode/issues/3) issue
## 1.5
Support new directive added in Laravel 5.3
### PHP
In some situations, it's useful to embed PHP code into your views. You can use the Blade `@php` directive to execute a block of plain PHP within your template:
```
@php
//
@endphp
```
### Include Sub-views
If you attempt to `@include` a view which does not exist, Laravel will throw an error. If you would like to include a view that may or may not be present, you should use the `@includeIf` directive:
```
@includeIf('view.name', ['some' => 'data'])
```
## 1.4
Update language mode recognition and emmet setting for VS Code 1.5+
## 1.3
Support Laravel 5.3 blade syntax
* `@verbatim` - displaying JavaScript variables in a large portion in template
```
@verbatim
<div class="container">
Hello, {{ name }}.
</div>
@endverbatim
```
* `$loop` variable : index, remaining, count, first, last, depth, parent
```
$loop->index
$loop->remaining
$loop->count
$loop->first
$loop->last
$loop->depth
$loop->parent
```
* Add pagination links helper snippet: `lv:pagination-links`

View file

@ -0,0 +1,9 @@
The MIT License (MIT)
Copyright (c) 2016 Winnie Lin
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,149 @@
# [Laravel Blade Snippets](https://marketplace.visualstudio.com/items?itemName=onecentlin.laravel-blade)
Laravel blade snippets and syntax highlight support for Visual Studio Code.
> Suggest Laravel related extension: [Laravel Snippets](https://marketplace.visualstudio.com/items?itemName=onecentlin.laravel5-snippets)
## Screenshot
![Demo](https://github.com/onecentlin/laravel-blade-snippets-vscode/raw/master/images/screenshot.gif)
## User Settings
Open `Preferences` -> `Settings`
```json
"emmet.triggerExpansionOnTab": true, // enable tab to expanse emmet tags
"blade.format.enable": true, // if you would like to enable blade format
```
Specific settings for blade language
```json
"[blade]": {
"editor.autoClosingBrackets": "always"
},
```
## Features
- Blade syntax highlight
- Blade snippets
- Emmet works in blade template
- Blade formatting
## Blade Syntax Hightlight
- Auto detected with `.blade.php` extension
- Manually switch language mode to `Blade` (`Ctrl + K, M` or `⌘ + K, M`)
## Laravel Blade Snippets
| Trigger | Snippet |
| ------------------- | ----------------------------------------- |
| b:extends | @extends |
| b:yield | @yield |
| b:section | @section...@endsection |
| b:section-show | @section...@show |
| b:if | @if...@endif |
| b:if-else | @if...@else...@endif |
| b:unless | @unless...@endunless |
| b:has-section | @hasSection...@else...@endif |
| b:for | @for...@endfor |
| b:foreach | @foreach...@endforeach |
| b:forelse | @forelse...@empty...@endforelse |
| b:while | @while...@endwhile |
| b:each | @each |
| b:push | @push...@endpush |
| b:stack | @stack |
| b:inject | @inject |
| b:comment | {{-- comment --}} (`Ctrl + /` or `⌘ + /`) |
| b:echo | {{ $data }} |
| b:echo-html | {!! $html !!} |
| b:echo-raw | @{{ variable }} |
| b:can | @can...@endcan (v5.1) |
| b:can-elsecan | @can...@elsecan...@endcan (v5.1) |
| b:canany | @canany...@endcanany (v5.8) |
| b:canany-elsecanany | @canany...@elsecanany...@endcanany (v5.8) |
| b:cannot | @cannot...@endcannot (v5.3) |
| b:cannot-elsecannot | @cannot...@elsecannot...@endcannot (v5.3) |
| b:verbatim | @verbatim...@endverbatim (v5.3) |
| b:php | @php...@endphp (v5.3) |
| b:includeIf | @includeIf (v5.3) |
| b:includeWhen | @includeWhen (v5.4) |
| b:includeFirst | @includeFirst (v5.5) |
| b:includeUnless | @includeUnless (v6.x) |
| b:component | @component...@endcomponent (v5.4) |
| b:slot | @slot...@endslot (v5.4) |
| b:isset | @isset...@endisset (v5.4) |
| b:empty | @empty...@endempty (v5.4) |
| b:auth | @auth...@endauth (v5.5) |
| b:guest | @guest...@endguest (v5.5) |
| b:switch | @switch...@case...@endswitch (v5.5) |
| b:lang | @lang |
| b:csrf | @csrf (v5.6) |
| b:method | @method(...) (v5.6) |
| b:dump | @dump(...) (v5.6) |
| b:error | @error...@enderror (v5.8) |
| b:props | @props (v7.4) |
| b:production | @production...@endproduction |
| b:env | @env...@endenv |
| b:once | @once...@endonce |
| b:class | @class (v8.51) |
| b:aware | @aware (v8.64) |
| b:js | @js (v8.71) |
| b:checked | @checked (v9.x) |
| b:selected | @selected (v9.x) |
| b:disabled | @disabled (v9.x) |
### $loop variable (Laravel v5.3+)
| Trigger | Snippet |
| ------------ | ------------------------------------------------------ |
| b:loop | $loop->(index,remaining,count,first,last,depth,parent) |
| b:loop-first | @if($loop->first)...@endif |
| b:loop-last | @if($loop->last)...@endif |
## Laravel Helper Snippets for Blade
| Trigger | Laravel Helper |
| ------------------- | --------------------- |
| lv:elixir | elixir() - deprecated |
| lv:mix | mix() (v5.4) |
| lv:trans | trans() |
| lv:action | action() |
| lv:secure-asset | secure_asset() |
| lv:url | url() |
| lv:asset | asset() |
| lv:route | route() |
| lv:csrf-field | csrf_field() |
| lv:csrf-token | csrf_token() |
| lv:pagination-links | $collection->links() |
## Blade extensions
Register in the `boot` method of `ServiceProvider`
- `Blade::component`
- `Blade::include`
- `Blade::if`
- `Blade::directive`
- `Blade::stringable`
Rendering inline blade templates
- `Blade::render`
- `Blade::renderComponent`
## Contact
Please file any [issues](https://github.com/onecentlin/laravel-blade-snippets-vscode/issues) or have a suggestion please tweet me [@onecentlin](https://twitter.com/onecentlin).
## Credits
- Blade language grammar is based on [Medalink syntax definition](https://github.com/Medalink/laravel-blade) for Sublime Text; Converted from [Blade templating support in Atom](https://github.com/jawee/language-blade)
- Textmate language format file is based on [Textmate bundle for Laravel 5](https://github.com/loranger/Laravel.tmbundle).
## License
Please read [License](https://github.com/onecentlin/laravel-blade-snippets-vscode/blob/master/LICENSE.md) for more information

View file

@ -0,0 +1,24 @@
{
"comments": {
"blockComment": [ "{{--", "--}}" ]
},
"brackets": [
["{", "}"],
["[", "]"],
["(", ")"]
],
"autoClosingPairs": [
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"]
],
"surroundingPairs": [
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"]
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

8539
my-snippets/laravel-blade/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,120 @@
{
"name": "laravel-blade",
"displayName": "Laravel Blade Snippets",
"description": "Laravel blade snippets and syntax highlight support",
"version": "1.32.0",
"publisher": "onecentlin",
"author": {
"name": "Winnie Lin",
"email": "onecentlin@gmail.com",
"url": "https://devmanna.blogspot.com"
},
"homepage": "https://github.com/onecentlin/laravel-blade-snippets-vscode",
"repository": {
"type": "git",
"url": "https://github.com/onecentlin/laravel-blade-snippets-vscode"
},
"bugs": {
"url": "https://github.com/onecentlin/laravel-blade-snippets-vscode/issues"
},
"engines": {
"vscode": "^1.46.0"
},
"keywords": [
"laravel",
"blade",
"template",
"snippet",
"formatter"
],
"icon": "images/icon.png",
"galleryBanner": {
"color": "#f66f62",
"theme": "dark"
},
"categories": [
"Programming Languages",
"Snippets",
"Formatters"
],
"main": "./out/extension.js",
"scripts": {
"build-srv": "cd ./server && npm install && tsc -p ./",
"compile": "tsc -watch -p ./",
"vscode:prepublish": "webpack --mode production && pushd \"./\" && npm run build-srv && popd",
"webpack": "webpack --mode development",
"webpack-dev": "webpack --mode development --watch"
},
"contributes": {
"languages": [
{
"id": "blade",
"aliases": [
"Blade",
"blade"
],
"extensions": [
".blade.php"
],
"configuration": "./blade.configuration.json"
}
],
"grammars": [
{
"language": "blade",
"scopeName": "text.html.php.blade",
"path": "./syntaxes/blade.tmLanguage.json",
"embeddedLanguages": {
"source.php": "php",
"source.css": "css",
"source.js": "javascript"
}
}
],
"snippets": [
{
"language": "blade",
"path": "./snippets/snippets.json"
},
{
"language": "blade",
"path": "./snippets/helpers.json"
},
{
"language": "blade",
"path": "./snippets/livewire.json"
},
{
"language": "php",
"path": "./snippets/blade.json"
}
],
"configuration": {
"title": "Blade Configuration",
"properties": {
"blade.format.enable": {
"type": "boolean",
"default": false,
"description": "Enable format blade file"
}
}
}
},
"activationEvents": [
"onLanguage:blade"
],
"devDependencies": {
"@types/node": "^16.0.0",
"@types/vscode": "^1.46.0",
"ts-loader": "^7.0.5",
"typescript": "^3.9.5",
"webpack": "^4.43.0",
"webpack-cli": "^4.9.1"
},
"dependencies": {
"vscode-css-languageservice": "^5.1.7",
"vscode-html-languageservice": "^4.1.0",
"vscode-languageclient": "^6.1.3",
"vscode-languageserver-types": "^3.16.0"
}
}

View file

@ -0,0 +1,6 @@
// ATTENTION - THIS DIRECTORY CONTAINS THIRD PARTY OPEN SOURCE MATERIALS:
[{
"name": "definitelytyped",
"repositoryURL": "https://github.com/DefinitelyTyped/DefinitelyTyped",
"license": "MIT"
}]

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,300 @@
{
"name": "vscode-html-languageserver",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "vscode-html-languageserver",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"typescript": "^2.7.1",
"vscode-css-languageservice": "^4.0.2",
"vscode-emmet-helper": "^1.2.15",
"vscode-html-languageservice": "^3.0.2",
"vscode-languageserver": "^4.0.0-next.4",
"vscode-languageserver-types": "^3.14.0",
"vscode-nls": "^4.1.1",
"vscode-uri": "^1.0.6"
},
"devDependencies": {
"@types/mocha": "2.2.33",
"@types/node": "7.0.43"
},
"engines": {
"node": "*"
}
},
"node_modules/@emmetio/extract-abbreviation": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/@emmetio/extract-abbreviation/-/extract-abbreviation-0.1.6.tgz",
"integrity": "sha512-Ce3xE2JvTSEbASFbRbA1gAIcMcZWdS2yUYRaQbeM0nbOzaZrUYfa3ePtcriYRZOZmr+CkKA+zbjhvTpIOAYVcw=="
},
"node_modules/@types/mocha": {
"version": "2.2.33",
"resolved": "http://registry.npm.taobao.org/@types/mocha/download/@types/mocha-2.2.33.tgz",
"integrity": "sha1-15oAYewnA3n02eIl9AlvtDZmne8=",
"dev": true
},
"node_modules/@types/node": {
"version": "7.0.43",
"resolved": "http://registry.npm.taobao.org/@types/node/download/@types/node-7.0.43.tgz",
"integrity": "sha1-oYfghJWgdfIAypRgeckU4aX+liw=",
"dev": true
},
"node_modules/jsonc-parser": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-1.0.3.tgz",
"integrity": "sha512-hk/69oAeaIzchq/v3lS50PXuzn5O2ynldopMC+SWBql7J2WtdptfB9dy8Y7+Og5rPkTCpn83zTiO8FMcqlXJ/g=="
},
"node_modules/typescript": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.7.1.tgz",
"integrity": "sha512-bqB1yS6o9TNA9ZC/MJxM0FZzPnZdtHj0xWK/IZ5khzVqdpGul/R/EIiHRgFXlwTD7PSIaYVnGKq1QgMCu2mnqw==",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
}
},
"node_modules/vscode-css-languageservice": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-4.0.2.tgz",
"integrity": "sha512-pTnfXbsME3pl+yDfhUp/mtvPyIJk0Le4zqJxDn56s9GY9LqY0RmkSEh0oHH6D0HXR3Ni6wKosIaqu8a2G0+jdw==",
"dependencies": {
"vscode-languageserver-types": "^3.15.0-next.2",
"vscode-nls": "^4.1.1"
}
},
"node_modules/vscode-css-languageservice/node_modules/vscode-languageserver-types": {
"version": "3.15.0-next.2",
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.15.0-next.2.tgz",
"integrity": "sha512-2JkrMWWUi2rlVLSo9OFR2PIGUzdiowEM8NgNYiwLKnXTjpwpjjIrJbNNxDik7Rv4oo9KtikcFQZKXbrKilL/MQ=="
},
"node_modules/vscode-css-languageservice/node_modules/vscode-nls": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-4.1.1.tgz",
"integrity": "sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A=="
},
"node_modules/vscode-emmet-helper": {
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/vscode-emmet-helper/-/vscode-emmet-helper-1.2.15.tgz",
"integrity": "sha512-JplvmMMWSvm/6/dZezix2ADPM49u6YahPYjs/QToohUpomW/2Eb27ecCrkCyOGBPfKLKGiOPHCssss8TSDA9ag==",
"deprecated": "This package has been renamed to @vscode/emmet-helper, please update to the new name",
"dependencies": {
"@emmetio/extract-abbreviation": "0.1.6",
"jsonc-parser": "^1.0.0",
"vscode-languageserver-types": "^3.6.0-next.1"
}
},
"node_modules/vscode-html-languageservice": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-3.0.2.tgz",
"integrity": "sha512-MP9al7nk1SqQwW4GdDy6Ec3UU1GKy0Wf4pzo3nQ5lgdScb2pajV7iyXZIGJk7jQbifkZWnG0jB7CKecTNFynJw==",
"dependencies": {
"vscode-languageserver-types": "^3.15.0-next.2",
"vscode-nls": "^4.1.1",
"vscode-uri": "^2.0.1"
}
},
"node_modules/vscode-html-languageservice/node_modules/vscode-languageserver-types": {
"version": "3.15.0-next.2",
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.15.0-next.2.tgz",
"integrity": "sha512-2JkrMWWUi2rlVLSo9OFR2PIGUzdiowEM8NgNYiwLKnXTjpwpjjIrJbNNxDik7Rv4oo9KtikcFQZKXbrKilL/MQ=="
},
"node_modules/vscode-html-languageservice/node_modules/vscode-nls": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-4.1.1.tgz",
"integrity": "sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A=="
},
"node_modules/vscode-html-languageservice/node_modules/vscode-uri": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-2.0.3.tgz",
"integrity": "sha512-4D3DI3F4uRy09WNtDGD93H9q034OHImxiIcSq664Hq1Y1AScehlP3qqZyTkX/RWxeu0MRMHGkrxYqm2qlDF/aw=="
},
"node_modules/vscode-jsonrpc": {
"version": "3.6.0-next.1",
"resolved": "http://registry.npm.taobao.org/vscode-jsonrpc/download/vscode-jsonrpc-3.6.0-next.1.tgz",
"integrity": "sha1-PLRj3/5YQtauwWcYypJScIzWqr4=",
"engines": {
"node": ">=4.0.0 || >=6.0.0"
}
},
"node_modules/vscode-languageserver": {
"version": "4.0.0-next.4",
"resolved": "http://registry.npm.taobao.org/vscode-languageserver/download/vscode-languageserver-4.0.0-next.4.tgz",
"integrity": "sha1-FiRAsVvtqrB+FnbwRuTZuFeLPZI=",
"dependencies": {
"vscode-languageserver-protocol": "^3.6.0-next.5",
"vscode-uri": "^1.0.1"
},
"bin": {
"installServerIntoExtension": "bin/installServerIntoExtension"
}
},
"node_modules/vscode-languageserver-protocol": {
"version": "3.6.0-next.5",
"resolved": "http://registry.npm.taobao.org/vscode-languageserver-protocol/download/vscode-languageserver-protocol-3.6.0-next.5.tgz",
"integrity": "sha1-7S7C23WYJvdTwKE5d9+yvtxNMbM=",
"dependencies": {
"vscode-jsonrpc": "^3.6.0-next.1",
"vscode-languageserver-types": "^3.6.0-next.1"
}
},
"node_modules/vscode-languageserver-types": {
"version": "3.14.0",
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.14.0.tgz",
"integrity": "sha512-lTmS6AlAlMHOvPQemVwo3CezxBp0sNB95KNPkqp3Nxd5VFEnuG1ByM0zlRWos0zjO3ZWtkvhal0COgiV1xIA4A=="
},
"node_modules/vscode-languageserver/node_modules/vscode-uri": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-1.0.8.tgz",
"integrity": "sha512-obtSWTlbJ+a+TFRYGaUumtVwb+InIUVI0Lu0VBUAPmj2cU5JutEXg3xUE0c2J5Tcy7h2DEKVJBFi+Y9ZSFzzPQ=="
},
"node_modules/vscode-nls": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-4.1.1.tgz",
"integrity": "sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A=="
},
"node_modules/vscode-uri": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-1.0.8.tgz",
"integrity": "sha512-obtSWTlbJ+a+TFRYGaUumtVwb+InIUVI0Lu0VBUAPmj2cU5JutEXg3xUE0c2J5Tcy7h2DEKVJBFi+Y9ZSFzzPQ=="
}
},
"dependencies": {
"@emmetio/extract-abbreviation": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/@emmetio/extract-abbreviation/-/extract-abbreviation-0.1.6.tgz",
"integrity": "sha512-Ce3xE2JvTSEbASFbRbA1gAIcMcZWdS2yUYRaQbeM0nbOzaZrUYfa3ePtcriYRZOZmr+CkKA+zbjhvTpIOAYVcw=="
},
"@types/mocha": {
"version": "2.2.33",
"resolved": "http://registry.npm.taobao.org/@types/mocha/download/@types/mocha-2.2.33.tgz",
"integrity": "sha1-15oAYewnA3n02eIl9AlvtDZmne8=",
"dev": true
},
"@types/node": {
"version": "7.0.43",
"resolved": "http://registry.npm.taobao.org/@types/node/download/@types/node-7.0.43.tgz",
"integrity": "sha1-oYfghJWgdfIAypRgeckU4aX+liw=",
"dev": true
},
"jsonc-parser": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-1.0.3.tgz",
"integrity": "sha512-hk/69oAeaIzchq/v3lS50PXuzn5O2ynldopMC+SWBql7J2WtdptfB9dy8Y7+Og5rPkTCpn83zTiO8FMcqlXJ/g=="
},
"typescript": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.7.1.tgz",
"integrity": "sha512-bqB1yS6o9TNA9ZC/MJxM0FZzPnZdtHj0xWK/IZ5khzVqdpGul/R/EIiHRgFXlwTD7PSIaYVnGKq1QgMCu2mnqw=="
},
"vscode-css-languageservice": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-4.0.2.tgz",
"integrity": "sha512-pTnfXbsME3pl+yDfhUp/mtvPyIJk0Le4zqJxDn56s9GY9LqY0RmkSEh0oHH6D0HXR3Ni6wKosIaqu8a2G0+jdw==",
"requires": {
"vscode-languageserver-types": "^3.15.0-next.2",
"vscode-nls": "^4.1.1"
},
"dependencies": {
"vscode-languageserver-types": {
"version": "3.15.0-next.2",
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.15.0-next.2.tgz",
"integrity": "sha512-2JkrMWWUi2rlVLSo9OFR2PIGUzdiowEM8NgNYiwLKnXTjpwpjjIrJbNNxDik7Rv4oo9KtikcFQZKXbrKilL/MQ=="
},
"vscode-nls": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-4.1.1.tgz",
"integrity": "sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A=="
}
}
},
"vscode-emmet-helper": {
"version": "1.2.15",
"resolved": "https://registry.npmjs.org/vscode-emmet-helper/-/vscode-emmet-helper-1.2.15.tgz",
"integrity": "sha512-JplvmMMWSvm/6/dZezix2ADPM49u6YahPYjs/QToohUpomW/2Eb27ecCrkCyOGBPfKLKGiOPHCssss8TSDA9ag==",
"requires": {
"@emmetio/extract-abbreviation": "0.1.6",
"jsonc-parser": "^1.0.0",
"vscode-languageserver-types": "^3.6.0-next.1"
}
},
"vscode-html-languageservice": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-3.0.2.tgz",
"integrity": "sha512-MP9al7nk1SqQwW4GdDy6Ec3UU1GKy0Wf4pzo3nQ5lgdScb2pajV7iyXZIGJk7jQbifkZWnG0jB7CKecTNFynJw==",
"requires": {
"vscode-languageserver-types": "^3.15.0-next.2",
"vscode-nls": "^4.1.1",
"vscode-uri": "^2.0.1"
},
"dependencies": {
"vscode-languageserver-types": {
"version": "3.15.0-next.2",
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.15.0-next.2.tgz",
"integrity": "sha512-2JkrMWWUi2rlVLSo9OFR2PIGUzdiowEM8NgNYiwLKnXTjpwpjjIrJbNNxDik7Rv4oo9KtikcFQZKXbrKilL/MQ=="
},
"vscode-nls": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-4.1.1.tgz",
"integrity": "sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A=="
},
"vscode-uri": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-2.0.3.tgz",
"integrity": "sha512-4D3DI3F4uRy09WNtDGD93H9q034OHImxiIcSq664Hq1Y1AScehlP3qqZyTkX/RWxeu0MRMHGkrxYqm2qlDF/aw=="
}
}
},
"vscode-jsonrpc": {
"version": "3.6.0-next.1",
"resolved": "http://registry.npm.taobao.org/vscode-jsonrpc/download/vscode-jsonrpc-3.6.0-next.1.tgz",
"integrity": "sha1-PLRj3/5YQtauwWcYypJScIzWqr4="
},
"vscode-languageserver": {
"version": "4.0.0-next.4",
"resolved": "http://registry.npm.taobao.org/vscode-languageserver/download/vscode-languageserver-4.0.0-next.4.tgz",
"integrity": "sha1-FiRAsVvtqrB+FnbwRuTZuFeLPZI=",
"requires": {
"vscode-languageserver-protocol": "^3.6.0-next.5",
"vscode-uri": "^1.0.1"
},
"dependencies": {
"vscode-uri": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-1.0.8.tgz",
"integrity": "sha512-obtSWTlbJ+a+TFRYGaUumtVwb+InIUVI0Lu0VBUAPmj2cU5JutEXg3xUE0c2J5Tcy7h2DEKVJBFi+Y9ZSFzzPQ=="
}
}
},
"vscode-languageserver-protocol": {
"version": "3.6.0-next.5",
"resolved": "http://registry.npm.taobao.org/vscode-languageserver-protocol/download/vscode-languageserver-protocol-3.6.0-next.5.tgz",
"integrity": "sha1-7S7C23WYJvdTwKE5d9+yvtxNMbM=",
"requires": {
"vscode-jsonrpc": "^3.6.0-next.1",
"vscode-languageserver-types": "^3.6.0-next.1"
}
},
"vscode-languageserver-types": {
"version": "3.14.0",
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.14.0.tgz",
"integrity": "sha512-lTmS6AlAlMHOvPQemVwo3CezxBp0sNB95KNPkqp3Nxd5VFEnuG1ByM0zlRWos0zjO3ZWtkvhal0COgiV1xIA4A=="
},
"vscode-nls": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-4.1.1.tgz",
"integrity": "sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A=="
},
"vscode-uri": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-1.0.8.tgz",
"integrity": "sha512-obtSWTlbJ+a+TFRYGaUumtVwb+InIUVI0Lu0VBUAPmj2cU5JutEXg3xUE0c2J5Tcy7h2DEKVJBFi+Y9ZSFzzPQ=="
}
}
}

View file

@ -0,0 +1,32 @@
{
"name": "vscode-html-languageserver",
"description": "HTML language server",
"version": "1.0.0",
"author": "Microsoft Corporation",
"license": "MIT",
"engines": {
"node": "*"
},
"dependencies": {
"typescript": "^2.7.1",
"vscode-css-languageservice": "^4.0.2",
"vscode-html-languageservice": "^3.0.2",
"vscode-emmet-helper": "^1.2.15",
"vscode-languageserver": "^4.0.0-next.4",
"vscode-languageserver-types": "^3.14.0",
"vscode-nls": "^4.1.1",
"vscode-uri": "^1.0.6"
},
"devDependencies": {
"@types/mocha": "2.2.33",
"@types/node": "7.0.43"
},
"scripts": {
"compile": "gulp compile-extension:html-server",
"watch": "gulp watch-extension:html-server",
"install-service-next": "yarn add vscode-css-languageservice@next && yarn add vscode-html-languageservice@next",
"install-service-local": "npm install ../../../../vscode-css-languageservice -f && npm install ../../../../vscode-html-languageservice -f",
"install-server-next": "yarn add vscode-languageserver@next",
"install-server-local": "npm install ../../../../vscode-languageserver-node/server -f"
}
}

View file

@ -0,0 +1,452 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { createConnection, IConnection, TextDocuments, InitializeParams, InitializeResult, RequestType, DocumentRangeFormattingRequest, Disposable, DocumentSelector, TextDocumentPositionParams, ServerCapabilities, Position, CompletionTriggerKind } from 'vscode-languageserver';
import { TextDocument, Diagnostic, DocumentLink, SymbolInformation, CompletionList } from 'vscode-languageserver-types';
import { getLanguageModes, LanguageModes, Settings } from './modes/languageModes';
import { ConfigurationRequest, ConfigurationParams } from 'vscode-languageserver-protocol/lib/protocol.configuration.proposed';
import { DocumentColorRequest, ServerCapabilities as CPServerCapabilities, ColorInformation, ColorPresentationRequest } from 'vscode-languageserver-protocol/lib/protocol.colorProvider.proposed';
import { DidChangeWorkspaceFoldersNotification, WorkspaceFolder } from 'vscode-languageserver-protocol/lib/protocol.workspaceFolders.proposed';
import { format } from './modes/formatting';
import { pushAll } from './utils/arrays';
import { getDocumentContext } from './utils/documentContext';
import uri from 'vscode-uri';
import { formatError, runSafe } from './utils/errors';
import { doComplete as emmetDoComplete, updateExtensionsPath as updateEmmetExtensionsPath, getEmmetCompletionParticipants } from 'vscode-emmet-helper';
namespace TagCloseRequest {
export const type: RequestType<TextDocumentPositionParams, string | null, any, any> = new RequestType('html/tag');
}
// Create a connection for the server
let connection: IConnection = createConnection();
console.log = connection.console.log.bind(connection.console);
console.error = connection.console.error.bind(connection.console);
process.on('unhandledRejection', (e: any) => {
connection.console.error(formatError(`Unhandled exception`, e));
});
// Create a simple text document manager. The text document manager
// supports full document sync only
let documents: TextDocuments = new TextDocuments();
// Make the text document manager listen on the connection
// for open, change and close text document events
documents.listen(connection);
let workspaceFolders: WorkspaceFolder[] | undefined;
var languageModes: LanguageModes;
let clientSnippetSupport = false;
let clientDynamicRegisterSupport = false;
let scopedSettingsSupport = false;
let workspaceFoldersSupport = false;
var globalSettings: Settings = {};
let documentSettings: { [key: string]: Thenable<Settings> } = {};
// remove document settings on close
documents.onDidClose(e => {
delete documentSettings[e.document.uri];
});
function getDocumentSettings(textDocument: TextDocument, needsDocumentSettings: () => boolean): Thenable<Settings | undefined> {
if (scopedSettingsSupport && needsDocumentSettings()) {
let promise = documentSettings[textDocument.uri];
if (!promise) {
let scopeUri = textDocument.uri;
let configRequestParam: ConfigurationParams = { items: [{ scopeUri, section: 'css' }, { scopeUri, section: 'html' }, { scopeUri, section: 'javascript' }] };
promise = connection.sendRequest(ConfigurationRequest.type, configRequestParam).then(s => ({ css: s[0], html: s[1], javascript: s[2] }));
documentSettings[textDocument.uri] = promise;
}
return promise;
}
return Promise.resolve(void 0);
}
let emmetSettings = {};
let currentEmmetExtensionsPath: string;
const emmetTriggerCharacters = ['!', '.', '}', ':', '*', '$', ']', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
// After the server has started the client sends an initilize request. The server receives
// in the passed params the rootPath of the workspace plus the client capabilites
connection.onInitialize((params: InitializeParams): InitializeResult => {
let initializationOptions = params.initializationOptions;
workspaceFolders = (<any>params).workspaceFolders;
if (!Array.isArray(workspaceFolders)) {
workspaceFolders = [];
if (params.rootPath) {
workspaceFolders.push({ name: '', uri: uri.file(params.rootPath).toString() });
}
}
languageModes = getLanguageModes(initializationOptions ? initializationOptions.embeddedLanguages : { css: true, javascript: true });
documents.onDidClose(e => {
languageModes.onDocumentRemoved(e.document);
});
connection.onShutdown(() => {
languageModes.dispose();
});
function hasClientCapability(...keys: string[]) {
let c = <any>params.capabilities;
for (let i = 0; c && i < keys.length; i++) {
c = c[keys[i]];
}
return !!c;
}
clientSnippetSupport = hasClientCapability('textDocument', 'completion', 'completionItem', 'snippetSupport');
clientDynamicRegisterSupport = hasClientCapability('workspace', 'symbol', 'dynamicRegistration');
scopedSettingsSupport = hasClientCapability('workspace', 'configuration');
workspaceFoldersSupport = hasClientCapability('workspace', 'workspaceFolders');
let capabilities: ServerCapabilities & CPServerCapabilities = {
// Tell the client that the server works in FULL text document sync mode
textDocumentSync: documents.syncKind,
completionProvider: clientSnippetSupport ? { resolveProvider: true, triggerCharacters: [...emmetTriggerCharacters, '.', ':', '<', '"', '=', '/'] } : undefined,
hoverProvider: true,
documentHighlightProvider: true,
documentRangeFormattingProvider: false,
documentSymbolProvider: true,
definitionProvider: true,
signatureHelpProvider: { triggerCharacters: ['('] },
referencesProvider: true,
colorProvider: true
};
return { capabilities };
});
connection.onInitialized((p) => {
if (workspaceFoldersSupport) {
connection.client.register(DidChangeWorkspaceFoldersNotification.type);
connection.onNotification(DidChangeWorkspaceFoldersNotification.type, e => {
let toAdd = e.event.added;
let toRemove = e.event.removed;
let updatedFolders = [];
if (workspaceFolders) {
for (let folder of workspaceFolders) {
if (!toRemove.some(r => r.uri === folder.uri) && !toAdd.some(r => r.uri === folder.uri)) {
updatedFolders.push(folder);
}
}
}
workspaceFolders = updatedFolders.concat(toAdd);
});
}
});
let formatterRegistration: Thenable<Disposable> | null = null;
// The settings have changed. Is send on server activation as well.
connection.onDidChangeConfiguration((change) => {
globalSettings = change.settings;
documentSettings = {}; // reset all document settings
languageModes.getAllModes().forEach(m => {
if (m.configure) {
m.configure(change.settings);
}
});
documents.all().forEach(triggerValidation);
// dynamically enable & disable the formatter
if (clientDynamicRegisterSupport) {
let enableFormatter = globalSettings && globalSettings.html && globalSettings.html.format && globalSettings.html.format.enable;
if (enableFormatter) {
if (!formatterRegistration) {
let documentSelector: DocumentSelector = [{ language: 'html' }, { language: 'handlebars' }]; // don't register razor, the formatter does more harm than good
formatterRegistration = connection.client.register(DocumentRangeFormattingRequest.type, { documentSelector });
}
} else if (formatterRegistration) {
formatterRegistration.then(r => r.dispose());
formatterRegistration = null;
}
}
emmetSettings = globalSettings.emmet;
if (currentEmmetExtensionsPath !== emmetSettings['extensionsPath']) {
currentEmmetExtensionsPath = emmetSettings['extensionsPath'];
const workspaceUri = (workspaceFolders && workspaceFolders.length === 1) ? uri.parse(workspaceFolders[0].uri) : null;
updateEmmetExtensionsPath(currentEmmetExtensionsPath, workspaceUri ? workspaceUri.fsPath : null);
}
});
let pendingValidationRequests: { [uri: string]: NodeJS.Timer } = {};
const validationDelayMs = 500;
// The content of a text document has changed. This event is emitted
// when the text document first opened or when its content has changed.
documents.onDidChangeContent(change => {
triggerValidation(change.document);
});
// a document has closed: clear all diagnostics
documents.onDidClose(event => {
cleanPendingValidation(event.document);
connection.sendDiagnostics({ uri: event.document.uri, diagnostics: [] });
});
function cleanPendingValidation(textDocument: TextDocument): void {
let request = pendingValidationRequests[textDocument.uri];
if (request) {
clearTimeout(request);
delete pendingValidationRequests[textDocument.uri];
}
}
function triggerValidation(textDocument: TextDocument): void {
cleanPendingValidation(textDocument);
pendingValidationRequests[textDocument.uri] = setTimeout(() => {
delete pendingValidationRequests[textDocument.uri];
validateTextDocument(textDocument);
}, validationDelayMs);
}
function isValidationEnabled(languageId: string, settings: Settings = globalSettings) {
let validationSettings = settings && settings.html && settings.html.validate;
if (validationSettings) {
return languageId === 'css' && validationSettings.styles !== false || languageId === 'javascript' && validationSettings.scripts !== false;
}
return true;
}
async function validateTextDocument(textDocument: TextDocument) {
try {
let diagnostics: Diagnostic[] = [];
if (textDocument.languageId === 'html') {
let modes = languageModes.getAllModesInDocument(textDocument);
let settings = await getDocumentSettings(textDocument, () => modes.some(m => !!m.doValidation));
modes.forEach(mode => {
if (mode.doValidation && isValidationEnabled(mode.getId(), settings)) {
pushAll(diagnostics, mode.doValidation(textDocument, settings));
}
});
}
connection.sendDiagnostics({ uri: textDocument.uri, diagnostics });
} catch (e) {
connection.console.error(formatError(`Error while validating ${textDocument.uri}`, e));
}
}
let cachedCompletionList: CompletionList;
const hexColorRegex = /^#[\d,a-f,A-F]{1,6}$/;
connection.onCompletion(async textDocumentPosition => {
return runSafe(async () => {
let document = documents.get(textDocumentPosition.textDocument.uri);
let mode = languageModes.getModeAtPosition(document, textDocumentPosition.position);
if (!mode || !mode.doComplete) {
return { isIncomplete: true, items: [] };
}
if (cachedCompletionList
&& !cachedCompletionList.isIncomplete
&& (mode.getId() === 'html' || mode.getId() === 'css')
&& textDocumentPosition.context
&& textDocumentPosition.context.triggerKind === CompletionTriggerKind.TriggerForIncompleteCompletions
) {
let result: CompletionList = emmetDoComplete(document, textDocumentPosition.position, mode.getId(), emmetSettings);
if (result && result.items) {
result.items.push(...cachedCompletionList.items);
} else {
result = cachedCompletionList;
cachedCompletionList = null;
}
return result;
}
if (mode.getId() !== 'html') {
connection.telemetry.logEvent({ key: 'html.embbedded.complete', value: { languageId: mode.getId() } });
}
cachedCompletionList = null;
let emmetCompletionList: CompletionList = {
isIncomplete: true,
items: undefined
};
if (mode.setCompletionParticipants) {
const emmetCompletionParticipant = getEmmetCompletionParticipants(document, textDocumentPosition.position, mode.getId(), emmetSettings, emmetCompletionList);
mode.setCompletionParticipants([emmetCompletionParticipant]);
}
let settings = await getDocumentSettings(document, () => mode.doComplete.length > 2);
let result = mode.doComplete(document, textDocumentPosition.position, settings);
if (emmetCompletionList && emmetCompletionList.items) {
cachedCompletionList = result;
if (emmetCompletionList.items.length && hexColorRegex.test(emmetCompletionList.items[0].label) && result.items.some(x => x.label === emmetCompletionList.items[0].label)) {
emmetCompletionList.items.shift();
}
return { isIncomplete: true, items: [...emmetCompletionList.items, ...result.items] };
}
return result;
}, null, `Error while computing completions for ${textDocumentPosition.textDocument.uri}`);
});
connection.onCompletionResolve(item => {
return runSafe(() => {
let data = item.data;
if (data && data.languageId && data.uri) {
let mode = languageModes.getMode(data.languageId);
let document = documents.get(data.uri);
if (mode && mode.doResolve && document) {
return mode.doResolve(document, item);
}
}
return item;
}, null, `Error while resolving completion proposal`);
});
connection.onHover(textDocumentPosition => {
return runSafe(() => {
let document = documents.get(textDocumentPosition.textDocument.uri);
let mode = languageModes.getModeAtPosition(document, textDocumentPosition.position);
if (mode && mode.doHover) {
return mode.doHover(document, textDocumentPosition.position);
}
return null;
}, null, `Error while computing hover for ${textDocumentPosition.textDocument.uri}`);
});
connection.onDocumentHighlight(documentHighlightParams => {
return runSafe(() => {
let document = documents.get(documentHighlightParams.textDocument.uri);
let mode = languageModes.getModeAtPosition(document, documentHighlightParams.position);
if (mode && mode.findDocumentHighlight) {
return mode.findDocumentHighlight(document, documentHighlightParams.position);
}
return [];
}, [], `Error while computing document highlights for ${documentHighlightParams.textDocument.uri}`);
});
connection.onDefinition(definitionParams => {
return runSafe(() => {
let document = documents.get(definitionParams.textDocument.uri);
let mode = languageModes.getModeAtPosition(document, definitionParams.position);
if (mode && mode.findDefinition) {
return mode.findDefinition(document, definitionParams.position);
}
return [];
}, null, `Error while computing definitions for ${definitionParams.textDocument.uri}`);
});
connection.onReferences(referenceParams => {
return runSafe(() => {
let document = documents.get(referenceParams.textDocument.uri);
let mode = languageModes.getModeAtPosition(document, referenceParams.position);
if (mode && mode.findReferences) {
return mode.findReferences(document, referenceParams.position);
}
return [];
}, [], `Error while computing references for ${referenceParams.textDocument.uri}`);
});
connection.onSignatureHelp(signatureHelpParms => {
return runSafe(() => {
let document = documents.get(signatureHelpParms.textDocument.uri);
let mode = languageModes.getModeAtPosition(document, signatureHelpParms.position);
if (mode && mode.doSignatureHelp) {
return mode.doSignatureHelp(document, signatureHelpParms.position);
}
return null;
}, null, `Error while computing signature help for ${signatureHelpParms.textDocument.uri}`);
});
connection.onDocumentRangeFormatting(async formatParams => {
return runSafe(async () => {
let document = documents.get(formatParams.textDocument.uri);
let settings = await getDocumentSettings(document, () => true);
if (!settings) {
settings = globalSettings;
}
let unformattedTags: string = settings && settings.html && settings.html.format && settings.html.format.unformatted || '';
let enabledModes = { css: !unformattedTags.match(/\bstyle\b/), javascript: !unformattedTags.match(/\bscript\b/) };
return format(languageModes, document, formatParams.range, formatParams.options, settings, enabledModes);
}, [], `Error while formatting range for ${formatParams.textDocument.uri}`);
});
connection.onDocumentLinks(documentLinkParam => {
return runSafe(() => {
let document = documents.get(documentLinkParam.textDocument.uri);
let links: DocumentLink[] = [];
if (document) {
let documentContext = getDocumentContext(document.uri, workspaceFolders);
languageModes.getAllModesInDocument(document).forEach(m => {
if (m.findDocumentLinks) {
pushAll(links, m.findDocumentLinks(document, documentContext));
}
});
}
return links;
}, [], `Error while document links for ${documentLinkParam.textDocument.uri}`);
});
connection.onDocumentSymbol(documentSymbolParms => {
return runSafe(() => {
let document = documents.get(documentSymbolParms.textDocument.uri);
let symbols: SymbolInformation[] = [];
languageModes.getAllModesInDocument(document).forEach(m => {
if (m.findDocumentSymbols) {
pushAll(symbols, m.findDocumentSymbols(document));
}
});
return symbols;
}, [], `Error while computing document symbols for ${documentSymbolParms.textDocument.uri}`);
});
connection.onRequest(DocumentColorRequest.type, params => {
return runSafe(() => {
let infos: ColorInformation[] = [];
let document = documents.get(params.textDocument.uri);
if (document) {
languageModes.getAllModesInDocument(document).forEach(m => {
if (m.findDocumentColors) {
pushAll(infos, m.findDocumentColors(document));
}
});
}
return infos;
}, [], `Error while computing document colors for ${params.textDocument.uri}`);
});
connection.onRequest(ColorPresentationRequest.type, params => {
return runSafe(() => {
let document = documents.get(params.textDocument.uri);
if (document) {
let mode = languageModes.getModeAtPosition(document, params.range.start);
if (mode && mode.getColorPresentations) {
return mode.getColorPresentations(document, params.color, params.range);
}
}
return [];
}, [], `Error while computing color presentations for ${params.textDocument.uri}`);
});
connection.onRequest(TagCloseRequest.type, params => {
return runSafe(() => {
let document = documents.get(params.textDocument.uri);
if (document) {
let pos = params.position;
if (pos.character > 0) {
let mode = languageModes.getModeAtPosition(document, Position.create(pos.line, pos.character - 1));
if (mode && mode.doAutoClose) {
return mode.doAutoClose(document, pos);
}
}
}
return null;
}, null, `Error while computing tag close actions for ${params.textDocument.uri}`);
});
// Listen on the connection
connection.listen();

View file

@ -0,0 +1,83 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TextDocument } from 'vscode-languageserver';
export interface LanguageModelCache<T> {
get(document: TextDocument): T;
onDocumentRemoved(document: TextDocument): void;
dispose(): void;
}
export function getLanguageModelCache<T>(maxEntries: number, cleanupIntervalTimeInSec: number, parse: (document: TextDocument) => T): LanguageModelCache<T> {
let languageModels: { [uri: string]: { version: number, languageId: string, cTime: number, languageModel: T } } = {};
let nModels = 0;
let cleanupInterval: NodeJS.Timer | undefined = void 0;
if (cleanupIntervalTimeInSec > 0) {
cleanupInterval = setInterval(() => {
let cutoffTime = Date.now() - cleanupIntervalTimeInSec * 1000;
let uris = Object.keys(languageModels);
for (let uri of uris) {
let languageModelInfo = languageModels[uri];
if (languageModelInfo.cTime < cutoffTime) {
delete languageModels[uri];
nModels--;
}
}
}, cleanupIntervalTimeInSec * 1000);
}
return {
get(document: TextDocument): T {
let version = document.version;
let languageId = document.languageId;
let languageModelInfo = languageModels[document.uri];
if (languageModelInfo && languageModelInfo.version === version && languageModelInfo.languageId === languageId) {
languageModelInfo.cTime = Date.now();
return languageModelInfo.languageModel;
}
let languageModel = parse(document);
languageModels[document.uri] = { languageModel, version, languageId, cTime: Date.now() };
if (!languageModelInfo) {
nModels++;
}
if (nModels === maxEntries) {
let oldestTime = Number.MAX_VALUE;
let oldestUri = null;
for (let uri in languageModels) {
let languageModelInfo = languageModels[uri];
if (languageModelInfo.cTime < oldestTime) {
oldestUri = uri;
oldestTime = languageModelInfo.cTime;
}
}
if (oldestUri) {
delete languageModels[oldestUri];
nModels--;
}
}
return languageModel;
},
onDocumentRemoved(document: TextDocument) {
let uri = document.uri;
if (languageModels[uri]) {
delete languageModels[uri];
nModels--;
}
},
dispose() {
if (typeof cleanupInterval !== 'undefined') {
clearInterval(cleanupInterval);
cleanupInterval = void 0;
languageModels = {};
nModels = 0;
}
}
};
}

View file

@ -0,0 +1,74 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { LanguageModelCache, getLanguageModelCache } from '../languageModelCache';
import { TextDocument, Position, Range } from 'vscode-languageserver-types';
import { getCSSLanguageService, Stylesheet, ICompletionParticipant } from 'vscode-css-languageservice';
import { LanguageMode, Settings } from './languageModes';
import { HTMLDocumentRegions, CSS_STYLE_RULE } from './embeddedSupport';
import { Color } from 'vscode-languageserver-protocol/lib/protocol.colorProvider.proposed';
export function getCSSMode(documentRegions: LanguageModelCache<HTMLDocumentRegions>): LanguageMode {
let cssLanguageService = getCSSLanguageService();
let embeddedCSSDocuments = getLanguageModelCache<TextDocument>(10, 60, document => documentRegions.get(document).getEmbeddedDocument('css'));
let cssStylesheets = getLanguageModelCache<Stylesheet>(10, 60, document => cssLanguageService.parseStylesheet(document));
return {
getId() {
return 'css';
},
configure(options: any) {
cssLanguageService.configure(options && options.css);
},
doValidation(document: TextDocument, settings?: Settings) {
let embedded = embeddedCSSDocuments.get(document);
return cssLanguageService.doValidation(embedded, cssStylesheets.get(embedded), settings && settings.css);
},
doComplete(document: TextDocument, position: Position) {
let embedded = embeddedCSSDocuments.get(document);
return cssLanguageService.doComplete(embedded, position, cssStylesheets.get(embedded));
},
setCompletionParticipants(registeredCompletionParticipants: ICompletionParticipant[]) {
cssLanguageService.setCompletionParticipants(registeredCompletionParticipants);
},
doHover(document: TextDocument, position: Position) {
let embedded = embeddedCSSDocuments.get(document);
return cssLanguageService.doHover(embedded, position, cssStylesheets.get(embedded));
},
findDocumentHighlight(document: TextDocument, position: Position) {
let embedded = embeddedCSSDocuments.get(document);
return cssLanguageService.findDocumentHighlights(embedded, position, cssStylesheets.get(embedded));
},
findDocumentSymbols(document: TextDocument) {
let embedded = embeddedCSSDocuments.get(document);
return cssLanguageService.findDocumentSymbols(embedded, cssStylesheets.get(embedded)).filter(s => s.name !== CSS_STYLE_RULE);
},
findDefinition(document: TextDocument, position: Position) {
let embedded = embeddedCSSDocuments.get(document);
return cssLanguageService.findDefinition(embedded, position, cssStylesheets.get(embedded));
},
findReferences(document: TextDocument, position: Position) {
let embedded = embeddedCSSDocuments.get(document);
return cssLanguageService.findReferences(embedded, position, cssStylesheets.get(embedded));
},
findDocumentColors(document: TextDocument) {
let embedded = embeddedCSSDocuments.get(document);
return cssLanguageService.findDocumentColors(embedded, cssStylesheets.get(embedded));
},
getColorPresentations(document: TextDocument, color: Color, range: Range) {
let embedded = embeddedCSSDocuments.get(document);
return cssLanguageService.getColorPresentations(embedded, cssStylesheets.get(embedded), color, range);
},
onDocumentRemoved(document: TextDocument) {
embeddedCSSDocuments.onDocumentRemoved(document);
cssStylesheets.onDocumentRemoved(document);
},
dispose() {
embeddedCSSDocuments.dispose();
cssStylesheets.dispose();
}
};
}

View file

@ -0,0 +1,233 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TextDocument, Position, LanguageService, TokenType, Range } from 'vscode-html-languageservice';
export interface LanguageRange extends Range {
languageId: string | undefined;
attributeValue?: boolean;
}
export interface HTMLDocumentRegions {
getEmbeddedDocument(languageId: string, ignoreAttributeValues?: boolean): TextDocument;
getLanguageRanges(range: Range): LanguageRange[];
getLanguageAtPosition(position: Position): string | undefined;
getLanguagesInDocument(): string[];
getImportedScripts(): string[];
}
export var CSS_STYLE_RULE = '__';
interface EmbeddedRegion { languageId: string | undefined; start: number; end: number; attributeValue?: boolean; }
export function getDocumentRegions(languageService: LanguageService, document: TextDocument): HTMLDocumentRegions {
let regions: EmbeddedRegion[] = [];
let scanner = languageService.createScanner(document.getText());
let lastTagName: string = '';
let lastAttributeName: string | null = null;
let languageIdFromType: string | undefined = undefined;
let importedScripts: string[] = [];
let token = scanner.scan();
while (token !== TokenType.EOS) {
switch (token) {
case TokenType.StartTag:
lastTagName = scanner.getTokenText();
lastAttributeName = null;
languageIdFromType = 'javascript';
break;
case TokenType.Styles:
regions.push({ languageId: 'css', start: scanner.getTokenOffset(), end: scanner.getTokenEnd() });
break;
case TokenType.Script:
regions.push({ languageId: languageIdFromType, start: scanner.getTokenOffset(), end: scanner.getTokenEnd() });
break;
case TokenType.AttributeName:
lastAttributeName = scanner.getTokenText();
break;
case TokenType.AttributeValue:
if (lastAttributeName === 'src' && lastTagName.toLowerCase() === 'script') {
let value = scanner.getTokenText();
if (value[0] === '\'' || value[0] === '"') {
value = value.substr(1, value.length - 1);
}
importedScripts.push(value);
} else if (lastAttributeName === 'type' && lastTagName.toLowerCase() === 'script') {
if (/["'](module|(text|application)\/(java|ecma)script)["']/.test(scanner.getTokenText())) {
languageIdFromType = 'javascript';
} else {
languageIdFromType = void 0;
}
} else {
let attributeLanguageId = getAttributeLanguage(lastAttributeName!);
if (attributeLanguageId) {
let start = scanner.getTokenOffset();
let end = scanner.getTokenEnd();
let firstChar = document.getText()[start];
if (firstChar === '\'' || firstChar === '"') {
start++;
end--;
}
regions.push({ languageId: attributeLanguageId, start, end, attributeValue: true });
}
}
lastAttributeName = null;
break;
}
token = scanner.scan();
}
return {
getLanguageRanges: (range: Range) => getLanguageRanges(document, regions, range),
getEmbeddedDocument: (languageId: string, ignoreAttributeValues: boolean) => getEmbeddedDocument(document, regions, languageId, ignoreAttributeValues),
getLanguageAtPosition: (position: Position) => getLanguageAtPosition(document, regions, position),
getLanguagesInDocument: () => getLanguagesInDocument(document, regions),
getImportedScripts: () => importedScripts
};
}
function getLanguageRanges(document: TextDocument, regions: EmbeddedRegion[], range: Range): LanguageRange[] {
let result: LanguageRange[] = [];
let currentPos = range ? range.start : Position.create(0, 0);
let currentOffset = range ? document.offsetAt(range.start) : 0;
let endOffset = range ? document.offsetAt(range.end) : document.getText().length;
for (let region of regions) {
if (region.end > currentOffset && region.start < endOffset) {
let start = Math.max(region.start, currentOffset);
let startPos = document.positionAt(start);
if (currentOffset < region.start) {
result.push({
start: currentPos,
end: startPos,
languageId: 'html'
});
}
let end = Math.min(region.end, endOffset);
let endPos = document.positionAt(end);
if (end > region.start) {
result.push({
start: startPos,
end: endPos,
languageId: region.languageId,
attributeValue: region.attributeValue
});
}
currentOffset = end;
currentPos = endPos;
}
}
if (currentOffset < endOffset) {
let endPos = range ? range.end : document.positionAt(endOffset);
result.push({
start: currentPos,
end: endPos,
languageId: 'html'
});
}
return result;
}
function getLanguagesInDocument(document: TextDocument, regions: EmbeddedRegion[]): string[] {
let result = [];
for (let region of regions) {
if (region.languageId && result.indexOf(region.languageId) === -1) {
result.push(region.languageId);
if (result.length === 3) {
return result;
}
}
}
result.push('html');
return result;
}
function getLanguageAtPosition(document: TextDocument, regions: EmbeddedRegion[], position: Position): string | undefined {
let offset = document.offsetAt(position);
for (let region of regions) {
if (region.start <= offset) {
if (offset <= region.end) {
return region.languageId;
}
} else {
break;
}
}
return 'html';
}
function getEmbeddedDocument(document: TextDocument, contents: EmbeddedRegion[], languageId: string, ignoreAttributeValues: boolean): TextDocument {
let currentPos = 0;
let oldContent = document.getText();
let result = '';
let lastSuffix = '';
for (let c of contents) {
if (c.languageId === languageId && (!ignoreAttributeValues || !c.attributeValue)) {
result = substituteWithWhitespace(result, currentPos, c.start, oldContent, lastSuffix, getPrefix(c));
result += oldContent.substring(c.start, c.end);
currentPos = c.end;
lastSuffix = getSuffix(c);
}
}
result = substituteWithWhitespace(result, currentPos, oldContent.length, oldContent, lastSuffix, '');
return TextDocument.create(document.uri, languageId, document.version, result);
}
function getPrefix(c: EmbeddedRegion) {
if (c.attributeValue) {
switch (c.languageId) {
case 'css': return CSS_STYLE_RULE + '{';
}
}
return '';
}
function getSuffix(c: EmbeddedRegion) {
if (c.attributeValue) {
switch (c.languageId) {
case 'css': return '}';
case 'javascript': return ';';
}
}
return '';
}
function substituteWithWhitespace(result: string, start: number, end: number, oldContent: string, before: string, after: string) {
let accumulatedWS = 0;
result += before;
for (let i = start + before.length; i < end; i++) {
let ch = oldContent[i];
if (ch === '\n' || ch === '\r') {
// only write new lines, skip the whitespace
accumulatedWS = 0;
result += ch;
} else {
accumulatedWS++;
}
}
result = append(result, ' ', accumulatedWS - after.length);
result += after;
return result;
}
function append(result: string, str: string, n: number): string {
while (n > 0) {
if (n & 1) {
result += str;
}
n >>= 1;
str += str;
}
return result;
}
function getAttributeLanguage(attributeName: string): string | null {
let match = attributeName.match(/^(style)$|^(on\w+)$/i);
if (!match) {
return null;
}
return match[1] ? 'css' : 'javascript';
}

View file

@ -0,0 +1,95 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { applyEdits } from '../utils/edits';
import { TextDocument, Range, TextEdit, FormattingOptions, Position } from 'vscode-languageserver-types';
import { LanguageModes, Settings, LanguageModeRange } from './languageModes';
import { pushAll } from '../utils/arrays';
import { isEOL } from '../utils/strings';
export function format(languageModes: LanguageModes, document: TextDocument, formatRange: Range, formattingOptions: FormattingOptions, settings: Settings | undefined, enabledModes: { [mode: string]: boolean }) {
let result: TextEdit[] = [];
let endPos = formatRange.end;
let endOffset = document.offsetAt(endPos);
let content = document.getText();
if (endPos.character === 0 && endPos.line > 0 && endOffset !== content.length) {
// if selection ends after a new line, exclude that new line
let prevLineStart = document.offsetAt(Position.create(endPos.line - 1, 0));
while (isEOL(content, endOffset - 1) && endOffset > prevLineStart) {
endOffset--;
}
formatRange = Range.create(formatRange.start, document.positionAt(endOffset));
}
// run the html formatter on the full range and pass the result content to the embedded formatters.
// from the final content create a single edit
// advantages of this approach are
// - correct indents in the html document
// - correct initial indent for embedded formatters
// - no worrying of overlapping edits
// make sure we start in html
let allRanges = languageModes.getModesInRange(document, formatRange);
let i = 0;
let startPos = formatRange.start;
let isHTML = (range: LanguageModeRange) => range.mode && range.mode.getId() === 'html';
while (i < allRanges.length && !isHTML(allRanges[i])) {
let range = allRanges[i];
if (!range.attributeValue && range.mode && range.mode.format) {
let edits = range.mode.format(document, Range.create(startPos, range.end), formattingOptions, settings);
pushAll(result, edits);
}
startPos = range.end;
i++;
}
if (i === allRanges.length) {
return result;
}
// modify the range
formatRange = Range.create(startPos, formatRange.end);
// perform a html format and apply changes to a new document
let htmlMode = languageModes.getMode('html')!;
let htmlEdits = htmlMode.format!(document, formatRange, formattingOptions, settings);
let htmlFormattedContent = applyEdits(document, htmlEdits);
let newDocument = TextDocument.create(document.uri + '.tmp', document.languageId, document.version, htmlFormattedContent);
try {
// run embedded formatters on html formatted content: - formatters see correct initial indent
let afterFormatRangeLength = document.getText().length - document.offsetAt(formatRange.end); // length of unchanged content after replace range
let newFormatRange = Range.create(formatRange.start, newDocument.positionAt(htmlFormattedContent.length - afterFormatRangeLength));
let embeddedRanges = languageModes.getModesInRange(newDocument, newFormatRange);
let embeddedEdits: TextEdit[] = [];
for (let r of embeddedRanges) {
let mode = r.mode;
if (mode && mode.format && enabledModes[mode.getId()] && !r.attributeValue) {
let edits = mode.format(newDocument, r, formattingOptions, settings);
for (let edit of edits) {
embeddedEdits.push(edit);
}
}
}
if (embeddedEdits.length === 0) {
pushAll(result, htmlEdits);
return result;
}
// apply all embedded format edits and create a single edit for all changes
let resultContent = applyEdits(newDocument, embeddedEdits);
let resultReplaceText = resultContent.substring(document.offsetAt(formatRange.start), resultContent.length - afterFormatRangeLength);
result.push(TextEdit.replace(formatRange, resultReplaceText));
return result;
} finally {
languageModes.onDocumentRemoved(newDocument);
}
}

View file

@ -0,0 +1,98 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { getLanguageModelCache } from '../languageModelCache';
import { LanguageService as HTMLLanguageService, HTMLDocument, DocumentContext, FormattingOptions, HTMLFormatConfiguration, TokenType } from 'vscode-html-languageservice';
import { TextDocument, Position, Range } from 'vscode-languageserver-types';
import { LanguageMode, Settings } from './languageModes';
export function getHTMLMode(htmlLanguageService: HTMLLanguageService): LanguageMode {
let globalSettings: Settings = {};
let htmlDocuments = getLanguageModelCache<HTMLDocument>(10, 60, document => htmlLanguageService.parseHTMLDocument(document));
let completionParticipants = [];
return {
getId() {
return 'html';
},
configure(options: any) {
globalSettings = options;
},
doComplete(document: TextDocument, position: Position, settings: Settings = globalSettings) {
let options = settings && settings.html && settings.html.suggest;
let doAutoComplete = settings && settings.html && settings.html.autoClosingTags;
if (doAutoComplete) {
options.hideAutoCompleteProposals = true;
}
const htmlDocument = htmlDocuments.get(document);
const offset = document.offsetAt(position);
const node = htmlDocument.findNodeBefore(offset);
const scanner = htmlLanguageService.createScanner(document.getText(), node.start);
let token = scanner.scan();
while (token !== TokenType.EOS && scanner.getTokenOffset() <= offset) {
if (token === TokenType.Content && offset <= scanner.getTokenEnd()) {
completionParticipants.forEach(participant => { if (participant.onHtmlContent) { participant.onHtmlContent(); } });
break;
}
token = scanner.scan();
}
return htmlLanguageService.doComplete(document, position, htmlDocument, options);
},
setCompletionParticipants(registeredCompletionParticipants: any[]) {
completionParticipants = registeredCompletionParticipants;
},
doHover(document: TextDocument, position: Position) {
return htmlLanguageService.doHover(document, position, htmlDocuments.get(document));
},
findDocumentHighlight(document: TextDocument, position: Position) {
return htmlLanguageService.findDocumentHighlights(document, position, htmlDocuments.get(document));
},
findDocumentLinks(document: TextDocument, documentContext: DocumentContext) {
return htmlLanguageService.findDocumentLinks(document, documentContext);
},
findDocumentSymbols(document: TextDocument) {
return htmlLanguageService.findDocumentSymbols(document, htmlDocuments.get(document));
},
format(document: TextDocument, range: Range, formatParams: FormattingOptions, settings: Settings = globalSettings) {
let formatSettings: HTMLFormatConfiguration = settings && settings.html && settings.html.format;
if (formatSettings) {
formatSettings = merge(formatSettings, {});
} else {
formatSettings = {};
}
if (formatSettings.contentUnformatted) {
formatSettings.contentUnformatted = formatSettings.contentUnformatted + ',script';
} else {
formatSettings.contentUnformatted = 'script';
}
formatSettings = merge(formatParams, formatSettings);
return htmlLanguageService.format(document, range, formatSettings);
},
doAutoClose(document: TextDocument, position: Position) {
let offset = document.offsetAt(position);
let text = document.getText();
if (offset > 0 && text.charAt(offset - 1).match(/[>\/]/g)) {
return htmlLanguageService.doTagComplete(document, position, htmlDocuments.get(document));
}
return null;
},
onDocumentRemoved(document: TextDocument) {
htmlDocuments.onDocumentRemoved(document);
},
dispose() {
htmlDocuments.dispose();
}
};
}
function merge(src: any, dst: any): any {
for (var key in src) {
if (src.hasOwnProperty(key)) {
dst[key] = src[key];
}
}
return dst;
}

View file

@ -0,0 +1,411 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { LanguageModelCache, getLanguageModelCache } from '../languageModelCache';
import { SymbolInformation, SymbolKind, CompletionItem, Location, SignatureHelp, SignatureInformation, ParameterInformation, Definition, TextEdit, TextDocument, Diagnostic, DiagnosticSeverity, Range, CompletionItemKind, Hover, MarkedString, DocumentHighlight, DocumentHighlightKind, CompletionList, Position, FormattingOptions } from 'vscode-languageserver-types';
import { LanguageMode, Settings } from './languageModes';
import { getWordAtText, startsWith, isWhitespaceOnly, repeat } from '../utils/strings';
import { HTMLDocumentRegions } from './embeddedSupport';
import * as ts from 'typescript';
import { join } from 'path';
const FILE_NAME = 'vscode://javascript/1'; // the same 'file' is used for all contents
const JQUERY_D_TS = join(__dirname, '../../lib/jquery.d.ts');
const JS_WORD_REGEX = /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g;
export function getJavascriptMode(documentRegions: LanguageModelCache<HTMLDocumentRegions>): LanguageMode {
let jsDocuments = getLanguageModelCache<TextDocument>(10, 60, document => documentRegions.get(document).getEmbeddedDocument('javascript'));
let compilerOptions: ts.CompilerOptions = { allowNonTsExtensions: true, allowJs: true, lib: ['lib.es6.d.ts'], target: ts.ScriptTarget.Latest, moduleResolution: ts.ModuleResolutionKind.Classic };
let currentTextDocument: TextDocument;
let scriptFileVersion: number = 0;
function updateCurrentTextDocument(doc: TextDocument) {
if (!currentTextDocument || doc.uri !== currentTextDocument.uri || doc.version !== currentTextDocument.version) {
currentTextDocument = jsDocuments.get(doc);
scriptFileVersion++;
}
}
const host: ts.LanguageServiceHost = {
getCompilationSettings: () => compilerOptions,
getScriptFileNames: () => [FILE_NAME, JQUERY_D_TS],
getScriptKind: () => ts.ScriptKind.JS,
getScriptVersion: (fileName: string) => {
if (fileName === FILE_NAME) {
return String(scriptFileVersion);
}
return '1'; // default lib an jquery.d.ts are static
},
getScriptSnapshot: (fileName: string) => {
let text = '';
if (startsWith(fileName, 'vscode:')) {
if (fileName === FILE_NAME) {
text = currentTextDocument.getText();
}
} else {
text = ts.sys.readFile(fileName) || '';
}
return {
getText: (start, end) => text.substring(start, end),
getLength: () => text.length,
getChangeRange: () => void 0
};
},
getCurrentDirectory: () => '',
getDefaultLibFileName: (options) => ts.getDefaultLibFilePath(options)
};
let jsLanguageService = ts.createLanguageService(host);
let globalSettings: Settings = {};
return {
getId() {
return 'javascript';
},
configure(options: any) {
globalSettings = options;
},
doValidation(document: TextDocument): Diagnostic[] {
updateCurrentTextDocument(document);
const syntaxDiagnostics = jsLanguageService.getSyntacticDiagnostics(FILE_NAME);
const semanticDiagnostics = jsLanguageService.getSemanticDiagnostics(FILE_NAME);
return syntaxDiagnostics.concat(semanticDiagnostics).map((diag: ts.Diagnostic): Diagnostic => {
return {
range: convertRange(currentTextDocument, diag),
severity: DiagnosticSeverity.Error,
source: 'js',
message: ts.flattenDiagnosticMessageText(diag.messageText, '\n')
};
});
},
doComplete(document: TextDocument, position: Position): CompletionList {
updateCurrentTextDocument(document);
let offset = currentTextDocument.offsetAt(position);
let completions = jsLanguageService.getCompletionsAtPosition(FILE_NAME, offset, { includeExternalModuleExports: false, includeInsertTextCompletions: false });
if (!completions) {
return { isIncomplete: false, items: [] };
}
let replaceRange = convertRange(currentTextDocument, getWordAtText(currentTextDocument.getText(), offset, JS_WORD_REGEX));
return {
isIncomplete: false,
items: completions.entries.map(entry => {
return {
uri: document.uri,
position: position,
label: entry.name,
sortText: entry.sortText,
kind: convertKind(entry.kind),
textEdit: TextEdit.replace(replaceRange, entry.name),
data: { // data used for resolving item details (see 'doResolve')
languageId: 'javascript',
uri: document.uri,
offset: offset
}
};
})
};
},
doResolve(document: TextDocument, item: CompletionItem): CompletionItem {
updateCurrentTextDocument(document);
let details = jsLanguageService.getCompletionEntryDetails(FILE_NAME, item.data.offset, item.label, undefined, undefined);
if (details) {
item.detail = ts.displayPartsToString(details.displayParts);
item.documentation = ts.displayPartsToString(details.documentation);
delete item.data;
}
return item;
},
doHover(document: TextDocument, position: Position): Hover | null {
updateCurrentTextDocument(document);
let info = jsLanguageService.getQuickInfoAtPosition(FILE_NAME, currentTextDocument.offsetAt(position));
if (info) {
let contents = ts.displayPartsToString(info.displayParts);
return {
range: convertRange(currentTextDocument, info.textSpan),
contents: MarkedString.fromPlainText(contents)
};
}
return null;
},
doSignatureHelp(document: TextDocument, position: Position): SignatureHelp | null {
updateCurrentTextDocument(document);
let signHelp = jsLanguageService.getSignatureHelpItems(FILE_NAME, currentTextDocument.offsetAt(position));
if (signHelp) {
let ret: SignatureHelp = {
activeSignature: signHelp.selectedItemIndex,
activeParameter: signHelp.argumentIndex,
signatures: []
};
signHelp.items.forEach(item => {
let signature: SignatureInformation = {
label: '',
documentation: undefined,
parameters: []
};
signature.label += ts.displayPartsToString(item.prefixDisplayParts);
item.parameters.forEach((p, i, a) => {
let label = ts.displayPartsToString(p.displayParts);
let parameter: ParameterInformation = {
label: label,
documentation: ts.displayPartsToString(p.documentation)
};
signature.label += label;
signature.parameters!.push(parameter);
if (i < a.length - 1) {
signature.label += ts.displayPartsToString(item.separatorDisplayParts);
}
});
signature.label += ts.displayPartsToString(item.suffixDisplayParts);
ret.signatures.push(signature);
});
return ret;
}
return null;
},
findDocumentHighlight(document: TextDocument, position: Position): DocumentHighlight[] {
updateCurrentTextDocument(document);
let occurrences = jsLanguageService.getOccurrencesAtPosition(FILE_NAME, currentTextDocument.offsetAt(position));
if (occurrences) {
return occurrences.map(entry => {
return {
range: convertRange(currentTextDocument, entry.textSpan),
kind: <DocumentHighlightKind>(entry.isWriteAccess ? DocumentHighlightKind.Write : DocumentHighlightKind.Text)
};
});
}
return [];
},
findDocumentSymbols(document: TextDocument): SymbolInformation[] {
updateCurrentTextDocument(document);
let items = jsLanguageService.getNavigationBarItems(FILE_NAME);
if (items) {
let result: SymbolInformation[] = [];
let existing = Object.create(null);
let collectSymbols = (item: ts.NavigationBarItem, containerLabel?: string) => {
let sig = item.text + item.kind + item.spans[0].start;
if (item.kind !== 'script' && !existing[sig]) {
let symbol: SymbolInformation = {
name: item.text,
kind: convertSymbolKind(item.kind),
location: {
uri: document.uri,
range: convertRange(currentTextDocument, item.spans[0])
},
containerName: containerLabel
};
existing[sig] = true;
result.push(symbol);
containerLabel = item.text;
}
if (item.childItems && item.childItems.length > 0) {
for (let child of item.childItems) {
collectSymbols(child, containerLabel);
}
}
};
items.forEach(item => collectSymbols(item));
return result;
}
return [];
},
findDefinition(document: TextDocument, position: Position): Definition | null {
updateCurrentTextDocument(document);
let definition = jsLanguageService.getDefinitionAtPosition(FILE_NAME, currentTextDocument.offsetAt(position));
if (definition) {
return definition.filter(d => d.fileName === FILE_NAME).map(d => {
return {
uri: document.uri,
range: convertRange(currentTextDocument, d.textSpan)
};
});
}
return null;
},
findReferences(document: TextDocument, position: Position): Location[] {
updateCurrentTextDocument(document);
let references = jsLanguageService.getReferencesAtPosition(FILE_NAME, currentTextDocument.offsetAt(position));
if (references) {
return references.filter(d => d.fileName === FILE_NAME).map(d => {
return {
uri: document.uri,
range: convertRange(currentTextDocument, d.textSpan)
};
});
}
return [];
},
format(document: TextDocument, range: Range, formatParams: FormattingOptions, settings: Settings = globalSettings): TextEdit[] {
currentTextDocument = documentRegions.get(document).getEmbeddedDocument('javascript', true);
scriptFileVersion++;
let formatterSettings = settings && settings.javascript && settings.javascript.format;
let initialIndentLevel = computeInitialIndent(document, range, formatParams);
let formatSettings = convertOptions(formatParams, formatterSettings, initialIndentLevel + 1);
let start = currentTextDocument.offsetAt(range.start);
let end = currentTextDocument.offsetAt(range.end);
let lastLineRange = null;
if (range.end.line > range.start.line && (range.end.character === 0 || isWhitespaceOnly(currentTextDocument.getText().substr(end - range.end.character, range.end.character)))) {
end -= range.end.character;
lastLineRange = Range.create(Position.create(range.end.line, 0), range.end);
}
let edits = jsLanguageService.getFormattingEditsForRange(FILE_NAME, start, end, formatSettings);
if (edits) {
let result = [];
for (let edit of edits) {
if (edit.span.start >= start && edit.span.start + edit.span.length <= end) {
result.push({
range: convertRange(currentTextDocument, edit.span),
newText: edit.newText
});
}
}
if (lastLineRange) {
result.push({
range: lastLineRange,
newText: generateIndent(initialIndentLevel, formatParams)
});
}
return result;
}
return [];
},
onDocumentRemoved(document: TextDocument) {
jsDocuments.onDocumentRemoved(document);
},
dispose() {
jsLanguageService.dispose();
jsDocuments.dispose();
}
};
}
function convertRange(document: TextDocument, span: { start: number | undefined, length: number | undefined }): Range {
if (typeof span.start === 'undefined') {
const pos = document.positionAt(0);
return Range.create(pos, pos);
}
const startPosition = document.positionAt(span.start);
const endPosition = document.positionAt(span.start + (span.length || 0));
return Range.create(startPosition, endPosition);
}
function convertKind(kind: string): CompletionItemKind {
switch (kind) {
case 'primitive type':
case 'keyword':
return CompletionItemKind.Keyword;
case 'var':
case 'local var':
return CompletionItemKind.Variable;
case 'property':
case 'getter':
case 'setter':
return CompletionItemKind.Field;
case 'function':
case 'method':
case 'construct':
case 'call':
case 'index':
return CompletionItemKind.Function;
case 'enum':
return CompletionItemKind.Enum;
case 'module':
return CompletionItemKind.Module;
case 'class':
return CompletionItemKind.Class;
case 'interface':
return CompletionItemKind.Interface;
case 'warning':
return CompletionItemKind.File;
}
return CompletionItemKind.Property;
}
function convertSymbolKind(kind: string): SymbolKind {
switch (kind) {
case 'var':
case 'local var':
case 'const':
return SymbolKind.Variable;
case 'function':
case 'local function':
return SymbolKind.Function;
case 'enum':
return SymbolKind.Enum;
case 'module':
return SymbolKind.Module;
case 'class':
return SymbolKind.Class;
case 'interface':
return SymbolKind.Interface;
case 'method':
return SymbolKind.Method;
case 'property':
case 'getter':
case 'setter':
return SymbolKind.Property;
}
return SymbolKind.Variable;
}
function convertOptions(options: FormattingOptions, formatSettings: any, initialIndentLevel: number): ts.FormatCodeOptions {
return {
ConvertTabsToSpaces: options.insertSpaces,
TabSize: options.tabSize,
IndentSize: options.tabSize,
IndentStyle: ts.IndentStyle.Smart,
NewLineCharacter: '\n',
BaseIndentSize: options.tabSize * initialIndentLevel,
InsertSpaceAfterCommaDelimiter: Boolean(!formatSettings || formatSettings.insertSpaceAfterCommaDelimiter),
InsertSpaceAfterSemicolonInForStatements: Boolean(!formatSettings || formatSettings.insertSpaceAfterSemicolonInForStatements),
InsertSpaceBeforeAndAfterBinaryOperators: Boolean(!formatSettings || formatSettings.insertSpaceBeforeAndAfterBinaryOperators),
InsertSpaceAfterKeywordsInControlFlowStatements: Boolean(!formatSettings || formatSettings.insertSpaceAfterKeywordsInControlFlowStatements),
InsertSpaceAfterFunctionKeywordForAnonymousFunctions: Boolean(!formatSettings || formatSettings.insertSpaceAfterFunctionKeywordForAnonymousFunctions),
InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: Boolean(formatSettings && formatSettings.insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis),
InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: Boolean(formatSettings && formatSettings.insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets),
InsertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: Boolean(formatSettings && formatSettings.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces),
InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: Boolean(formatSettings && formatSettings.insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces),
PlaceOpenBraceOnNewLineForControlBlocks: Boolean(formatSettings && formatSettings.placeOpenBraceOnNewLineForFunctions),
PlaceOpenBraceOnNewLineForFunctions: Boolean(formatSettings && formatSettings.placeOpenBraceOnNewLineForControlBlocks)
};
}
function computeInitialIndent(document: TextDocument, range: Range, options: FormattingOptions) {
let lineStart = document.offsetAt(Position.create(range.start.line, 0));
let content = document.getText();
let i = lineStart;
let nChars = 0;
let tabSize = options.tabSize || 4;
while (i < content.length) {
let ch = content.charAt(i);
if (ch === ' ') {
nChars++;
} else if (ch === '\t') {
nChars += tabSize;
} else {
break;
}
i++;
}
return Math.floor(nChars / tabSize);
}
function generateIndent(level: number, options: FormattingOptions) {
if (options.insertSpaces) {
return repeat(' ', level * options.tabSize);
} else {
return repeat('\t', level);
}
}

View file

@ -0,0 +1,143 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { getLanguageService as getHTMLLanguageService, DocumentContext } from 'vscode-html-languageservice';
import {
CompletionItem, Location, SignatureHelp, Definition, TextEdit, TextDocument, Diagnostic, DocumentLink, Range,
Hover, DocumentHighlight, CompletionList, Position, FormattingOptions, SymbolInformation
} from 'vscode-languageserver-types';
import { ColorInformation, ColorPresentation, Color } from 'vscode-languageserver-protocol/lib/protocol.colorProvider.proposed';
import { getLanguageModelCache, LanguageModelCache } from '../languageModelCache';
import { getDocumentRegions, HTMLDocumentRegions } from './embeddedSupport';
import { getCSSMode } from './cssMode';
import { getJavascriptMode } from './javascriptMode';
import { getHTMLMode } from './htmlMode';
export { ColorInformation, ColorPresentation, Color };
export interface Settings {
css?: any;
html?: any;
javascript?: any;
emmet?: { [key: string]: any };
}
export interface SettingProvider {
getDocumentSettings(textDocument: TextDocument): Thenable<Settings>;
}
export interface LanguageMode {
getId(): string;
configure?: (options: Settings) => void;
doValidation?: (document: TextDocument, settings?: Settings) => Diagnostic[];
doComplete?: (document: TextDocument, position: Position, settings?: Settings) => CompletionList | null;
setCompletionParticipants?: (registeredCompletionParticipants: any[]) => void;
doResolve?: (document: TextDocument, item: CompletionItem) => CompletionItem | null;
doHover?: (document: TextDocument, position: Position) => Hover | null;
doSignatureHelp?: (document: TextDocument, position: Position) => SignatureHelp | null;
findDocumentHighlight?: (document: TextDocument, position: Position) => DocumentHighlight[];
findDocumentSymbols?: (document: TextDocument) => SymbolInformation[];
findDocumentLinks?: (document: TextDocument, documentContext: DocumentContext) => DocumentLink[];
findDefinition?: (document: TextDocument, position: Position) => Definition | null;
findReferences?: (document: TextDocument, position: Position) => Location[];
format?: (document: TextDocument, range: Range, options: FormattingOptions, settings?: Settings) => TextEdit[];
findDocumentColors?: (document: TextDocument) => ColorInformation[];
getColorPresentations?: (document: TextDocument, color: Color, range: Range) => ColorPresentation[];
doAutoClose?: (document: TextDocument, position: Position) => string | null;
onDocumentRemoved(document: TextDocument): void;
dispose(): void;
}
export interface LanguageModes {
getModeAtPosition(document: TextDocument, position: Position): LanguageMode | undefined;
getModesInRange(document: TextDocument, range: Range): LanguageModeRange[];
getAllModes(): LanguageMode[];
getAllModesInDocument(document: TextDocument): LanguageMode[];
getMode(languageId: string): LanguageMode | undefined;
onDocumentRemoved(document: TextDocument): void;
dispose(): void;
}
export interface LanguageModeRange extends Range {
mode: LanguageMode | undefined;
attributeValue?: boolean;
}
export function getLanguageModes(supportedLanguages: { [languageId: string]: boolean; }): LanguageModes {
var htmlLanguageService = getHTMLLanguageService();
let documentRegions = getLanguageModelCache<HTMLDocumentRegions>(10, 60, document => getDocumentRegions(htmlLanguageService, document));
let modelCaches: LanguageModelCache<any>[] = [];
modelCaches.push(documentRegions);
let modes = Object.create(null);
modes['html'] = getHTMLMode(htmlLanguageService);
if (supportedLanguages['css']) {
modes['css'] = getCSSMode(documentRegions);
}
if (supportedLanguages['javascript']) {
modes['javascript'] = getJavascriptMode(documentRegions);
}
return {
getModeAtPosition(document: TextDocument, position: Position): LanguageMode | undefined {
let languageId = documentRegions.get(document).getLanguageAtPosition(position);
if (languageId) {
return modes[languageId];
}
return void 0;
},
getModesInRange(document: TextDocument, range: Range): LanguageModeRange[] {
return documentRegions.get(document).getLanguageRanges(range).map(r => {
return <LanguageModeRange>{
start: r.start,
end: r.end,
mode: r.languageId && modes[r.languageId],
attributeValue: r.attributeValue
};
});
},
getAllModesInDocument(document: TextDocument): LanguageMode[] {
let result = [];
for (let languageId of documentRegions.get(document).getLanguagesInDocument()) {
let mode = modes[languageId];
if (mode) {
result.push(mode);
}
}
return result;
},
getAllModes(): LanguageMode[] {
let result = [];
for (let languageId in modes) {
let mode = modes[languageId];
if (mode) {
result.push(mode);
}
}
return result;
},
getMode(languageId: string): LanguageMode {
return modes[languageId];
},
onDocumentRemoved(document: TextDocument) {
modelCaches.forEach(mc => mc.onDocumentRemoved(document));
for (let mode in modes) {
modes[mode].onDocumentRemoved(document);
}
},
dispose(): void {
modelCaches.forEach(mc => mc.dispose());
modelCaches = [];
for (let mode in modes) {
modes[mode].dispose();
}
modes = {};
}
};
}

View file

@ -0,0 +1,13 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
export function pushAll<T>(to: T[], from: T[]) {
if (from) {
for (var i = 0; i < from.length; i++) {
to.push(from[i]);
}
}
}

View file

@ -0,0 +1,40 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { DocumentContext } from 'vscode-html-languageservice';
import { endsWith, startsWith } from '../utils/strings';
import * as url from 'url';
import { WorkspaceFolder } from 'vscode-languageserver-protocol/lib/protocol.workspaceFolders.proposed';
export function getDocumentContext(documentUri: string, workspaceFolders: WorkspaceFolder[]): DocumentContext {
function getRootFolder(): string | undefined {
for (let folder of workspaceFolders) {
let folderURI = folder.uri;
if (!endsWith(folderURI, '/')) {
folderURI = folderURI + '/';
}
if (startsWith(documentUri, folderURI)) {
return folderURI;
}
}
return void 0;
}
return {
resolveReference: (ref, base = documentUri) => {
if (ref[0] === '/') { // resolve absolute path against the current workspace folder
if (startsWith(base, 'file://')) {
let folderUri = getRootFolder();
if (folderUri) {
return folderUri + ref.substr(1);
}
}
}
return url.resolve(base, ref);
},
};
}

View file

@ -0,0 +1,32 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TextDocument, TextEdit, Position } from 'vscode-languageserver-types';
export function applyEdits(document: TextDocument, edits: TextEdit[]): string {
let text = document.getText();
let sortedEdits = edits.sort((a, b) => {
let startDiff = comparePositions(a.range.start, b.range.start);
if (startDiff === 0) {
return comparePositions(a.range.end, b.range.end);
}
return startDiff;
});
sortedEdits.forEach(e => {
let startOffset = document.offsetAt(e.range.start);
let endOffset = document.offsetAt(e.range.end);
text = text.substring(0, startOffset) + e.newText + text.substring(endOffset, text.length);
});
return text;
}
function comparePositions(p1: Position, p2: Position) {
let diff = p2.line - p1.line;
if (diff === 0) {
return p2.character - p1.character;
}
return diff;
}

View file

@ -0,0 +1,33 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
export function formatError(message: string, err: any): string {
if (err instanceof Error) {
let error = <Error>err;
return `${message}: ${error.message}\n${error.stack}`;
} else if (typeof err === 'string') {
return `${message}: ${err}`;
} else if (err) {
return `${message}: ${err.toString()}`;
}
return message;
}
export function runSafe<T>(func: () => Thenable<T> | T, errorVal: T, errorMessage: string): Thenable<T> | T {
try {
let t = func();
if (t instanceof Promise) {
return t.then(void 0, e => {
console.error(formatError(errorMessage, e));
return errorVal;
});
}
return t;
} catch (e) {
console.error(formatError(errorMessage, e));
return errorVal;
}
}

View file

@ -0,0 +1,79 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
export function getWordAtText(text: string, offset: number, wordDefinition: RegExp): { start: number, length: number } {
let lineStart = offset;
while (lineStart > 0 && !isNewlineCharacter(text.charCodeAt(lineStart - 1))) {
lineStart--;
}
let offsetInLine = offset - lineStart;
let lineText = text.substr(lineStart);
// make a copy of the regex as to not keep the state
let flags = wordDefinition.ignoreCase ? 'gi' : 'g';
wordDefinition = new RegExp(wordDefinition.source, flags);
let match = wordDefinition.exec(lineText);
while (match && match.index + match[0].length < offsetInLine) {
match = wordDefinition.exec(lineText);
}
if (match && match.index <= offsetInLine) {
return { start: match.index + lineStart, length: match[0].length };
}
return { start: offset, length: 0 };
}
export function startsWith(haystack: string, needle: string): boolean {
if (haystack.length < needle.length) {
return false;
}
for (let i = 0; i < needle.length; i++) {
if (haystack[i] !== needle[i]) {
return false;
}
}
return true;
}
export function endsWith(haystack: string, needle: string): boolean {
let diff = haystack.length - needle.length;
if (diff > 0) {
return haystack.indexOf(needle, diff) === diff;
} else if (diff === 0) {
return haystack === needle;
} else {
return false;
}
}
export function repeat(value: string, count: number) {
var s = '';
while (count > 0) {
if ((count & 1) === 1) {
s += value;
}
value += value;
count = count >>> 1;
}
return s;
}
export function isWhitespaceOnly(str: string) {
return /^\s*$/.test(str);
}
export function isEOL(content: string, offset: number) {
return isNewlineCharacter(content.charCodeAt(offset));
}
const CR = '\r'.charCodeAt(0);
const NL = '\n'.charCodeAt(0);
export function isNewlineCharacter(charCode: number) {
return charCode === CR || charCode === NL;
}

View file

@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"outDir": "./out",
"noUnusedLocals": true,
"lib": [
"es5", "es2015.promise"
]
},
"exclude": [
"**/jquery.d.ts"
]
}

View file

@ -0,0 +1,49 @@
{
"Blade-component": {
"prefix": "Blade::component",
"body": "Blade::component('${1:package-name}', ${2:PackageNameComponent}::class);",
"description": "Registering Package Components (AppServiceProvider boot method)"
},
"Blade-include": {
"prefix": "Blade::include",
"body": "Blade::include('${1:includes.input}', '${2:input}');",
"description": "Aliasing Includes (AppServiceProvider boot method)"
},
"Blade-if": {
"prefix": "Blade::if",
"body": [
"Blade::if('${1:env}', function ($${2:environment}) {",
" ${3:return app()->environment($$environment);}",
"});"
],
"description": "Custom If Statements (AppServiceProvider boot method)"
},
"Blade-directive": {
"prefix": "Blade::directive",
"body": [
"Blade::directive('${1:datetime}', function ($${2:expression}) {",
" ${3:return \"<?php echo ($$expression)->format('m/d/Y H:i'); ?>\";}",
"});"
],
"description": "Custom directive (AppServiceProvider boot method)"
},
"Blade-stringable": {
"prefix": "Blade::stringable",
"body": [
"Blade::stringable(function (${1:Money} $${2:money}) {",
" ${3:return $$money->formatTo('en_GB');}",
"});"
],
"description": "Custom echo handlers (AppServiceProvider boot method)"
},
"Blade-render": {
"prefix": "Blade::render",
"body": "Blade::render(${1:'Blade template string'}, ${2:\\$data});",
"description": "Transform a raw Blade template string into valid HTML (Laravel 9.x)"
},
"Blade-renderComponent": {
"prefix": "Blade::renderComponent",
"body": "Blade::renderComponent(new ${1:HelloComponent}(${2:\\$params}));",
"description": "Render a given class component by passing the component instance to the method (Laravel 9.x)"
}
}

View file

@ -0,0 +1,62 @@
{
/* Paths */
"Path-elixir": {
"prefix": "lv:elixir",
"body": "{{ elixir('${1:file}') }}",
"description": "(deprecated) elixir path"
},
"Path-mix": {
"prefix": "lv:mix",
"body": "{{ mix('${1:file}') }}",
"description": "mix path"
},
/* Strings */
"String-trans": {
"prefix": "lv:trans",
"body": "{{ trans('$1') }}",
"description": "trans"
},
/* URLs */
"URL-action": {
"prefix": "lv:action",
"body": "{{ action('${1:ControllerName}', [${2:'id'=>1}]) }}",
"description": "URL-action"
},
"URL-secure-asset": {
"prefix": "lv:secure-asset",
"body": "{{ secure_asset('$1', ${2:\\$title}, ${3:\\$attributes=[]}) }}",
"description": "URL-secure-asset"
},
"URL-url": {
"prefix": "lv:url",
"body": "{{ url('${1:url}', [$2]) }}",
"description": "URL-url"
},
"URL-asset": {
"prefix": "lv:asset",
"body": "{{ asset('$1') }}",
"description": "URL-asset"
},
"URL-route": {
"prefix": "lv:route",
"body": "{{ route('${1:routeName}', [${2:'id'=>1}]) }}",
"description": "URL-route"
},
/* Miscellaneous */
"Form-csrf-field": {
"prefix": "lv:csrf-field",
"body": "{{ csrf_field() }}",
"description": "CSRF hidden field"
},
"csrf-token": {
"prefix": "lv:csrf-token",
"body": "{{ csrf_token() }}",
"description": "CSRF token"
},
/* Paginate */
"Paginate-links": {
"prefix": "lv:pagination-links",
"body": "{{ \\$${1:collection}->links() }}",
"description": "pagination links"
}
}

View file

@ -0,0 +1,17 @@
{
"livewireStyles": {
"prefix": "livewire:styles",
"body": "@livewireStyles",
"description": "Livewire Styles directive"
},
"livewireScripts": {
"prefix": "livewire:scripts",
"body": "@livewireScripts",
"description": "Livewire Scripts directive"
},
"livewire-component": {
"prefix": "livewire:component",
"body": "@livewire('${1:component}', ['${2:user}' => \\$${3:user}]${4:, key(\\$$3->id)})",
"description": "Livewire nesting components"
}
}

View file

@ -0,0 +1,460 @@
{
/* Extending a layout */
"Extend layout": {
"prefix": "b:extends",
"body": "@extends('${1:name}')",
"description": "extends view layout"
},
"Yield content": {
"prefix": "b:yield",
"body": "@yield('${1:name}')",
"description": "yield content section"
},
"Content Section": {
"prefix": "b:section",
"body": [
"@section('${1:name}')",
" $2",
"@endsection"
],
"description": "content section"
},
"Content Section Show": {
"prefix": "b:section-show",
"body": [
"@section('$1')",
" $2",
"@show"
],
"description": "content section show"
},
/* Include sub-view */
"Include view": {
"prefix": "b:include",
"body": "@include('${1:name}')",
"description": "include view"
},
/* If Statements */
"If-block": {
"prefix": "b:if",
"body": [
"@if ($1)",
" $2",
"@endif"
],
"description": "@if block"
},
"If-else-block": {
"prefix": "b:if-else",
"body": [
"@if ($1)",
" $2",
"@else",
" $3",
"@endif"
],
"description": "if-else block"
},
"Has Section": {
"prefix": "b:has-section",
"body": [
"@hasSection ('${1:name}')",
" $2",
"@else",
" $3",
"@endif"
],
"description": "@hasSection condition"
},
"Unless-block": {
"prefix": "b:unless",
"body": [
"@unless ($1)",
" $2",
"@endunless"
],
"description": "@unless block"
},
/* Loops */
"For-block": {
"prefix": "b:for",
"body": [
"@for (\\$i = ${1:0}; \\$i < ${2:\\$count}; \\$i++)",
" $3",
"@endfor"
],
"description": "@for block"
},
"Foreach-block": {
"prefix": "b:foreach",
"body": [
"@foreach (${1:\\$collection} as ${2:\\$item})",
" $3",
"@endforeach"
],
"description": "@foreach block"
},
"forelse-block": {
"prefix": "b:forelse",
"body": [
"@forelse (${1:\\$collection} as ${2:\\$item})",
" $3",
"@empty",
" $4",
"@endforelse"
],
"description": "@forelse block"
},
"while-block": {
"prefix": "b:while",
"body": [
"@while ($1)",
" $2",
"@endwhile"
],
"description": "@while block"
},
/* Rendering views for collections */
"each loop": {
"prefix": "b:each",
"body": "@each('${1:view.name}', ${2:\\$collection}, '${3:variable}', '${4:view.empty}')",
"description": "@each loop"
},
/* Comments */
"blade comment": {
"prefix": "b:comment",
"body": "{{-- ${1:comment} --}}",
"description": "comment block"
},
/* Display Data */
"blade echo-data": {
"prefix": "b:echo",
"body": "{{ ${1:\\$data} }}",
"description": "echo data"
},
"blade echo-unescaped-data": {
"prefix": "b:echo-html",
"body": "{!! ${1:\\$html_data} !!}",
"description": "echo unescaped data (allow html outputs)"
},
"blade echo-untouch": {
"prefix": "b:echo-raw",
"body": "@{{ ${1:variable} }}",
"description": "echo untouched data (allow javascript expression)"
},
"blade verbatim": {
"prefix": "b:verbatim",
"body": [
"@verbatim",
"{{ ${1:variable} }}",
"@endverbatim"
],
"description": "displaying JavaScript variables in a large portion of your template"
},
/* Stacks */
"Push stack": {
"prefix": "b:push",
"body": [
"@push('${1:name}')",
" $2",
"@endpush"
],
"description": "@push stack"
},
"Stack": {
"prefix": "b:stack",
"body": "@stack('${1:name}')",
"description": "@stack"
},
/* Service Injection */
"inject service": {
"prefix": "b:inject",
"body": "@inject('${1:name}', '${2:class}')",
"description": "@inject Service"
},
/* Authorizing */
"can": {
"prefix": "b:can",
"body": [
"@can('${1:update}', ${2:\\$post})",
" $3",
"@endcan"
],
"description": "display a portion of the page only if the user is authorized to perform a given action."
},
"can-elsecan": {
"prefix": "b:can-elsecan",
"body": [
"@can('${1:update}', ${2:\\$post})",
" $3",
"@elsecan('create', App\\Models\\\\${4:Post}::class)",
" $5",
"@endcan"
],
"description": "display a portion of the page only if the user is authorized to perform a given action."
},
"canany": {
"prefix": "b:canany",
"body": [
"@canany(['update', 'view', 'delete'], ${1:\\$post})",
" $2",
"@endcanany"
],
"description": "display a portion of the page only if the user is authorized to perform a given action."
},
"canany-elsecanany": {
"prefix": "b:canany-elsecanany",
"body": [
"@canany(['update', 'view', 'delete'], ${1:\\$post})",
" $2",
"@elsecanany(['create'], App\\Models\\\\${3:Post}::class)",
" $4",
"@endcanany"
],
"description": "display a portion of the page only if the user is authorized to perform a given action."
},
"cannot": {
"prefix": "b:cannot",
"body": [
"@cannot('${1:update}', ${2:\\$post})",
" $3",
"@endcannot"
],
"description": "display a portion of the page only if the user is authorized to perform a given action."
},
"cannot-elsecannot": {
"prefix": "b:cannot-elsecannot",
"body": [
"@cannot('${1:update}', ${2:\\$post})",
" $3",
"@elsecannot('create', App\\Models\\\\${5:Post}::class)",
" $6",
"@endcannot"
],
"description": "display a portion of the page only if the user is authorized to perform a given action."
},
/* v5.3 - $loop variable */
"loop": {
"prefix": "b:loop",
"body": [
"\\$loop->${1:first}"
],
"description": "$loop->(index|remaining|count|first|last|depth|parent)"
},
"loop first": {
"prefix": "b:loop-first",
"body": [
"@if (\\$loop->first)",
" ${1:{{-- This is the first iteration --\\}\\}}",
"@endif"
],
"description": "$loop->first"
},
"loop last": {
"prefix": "b:loop-last",
"body": [
"@if (\\$loop->last)",
" ${1:{{-- This is the last iteration --\\}\\}}",
"@endif"
],
"description": "$loop->last"
},
"php": {
"prefix": "b:php",
"body": [
"@php",
" $1",
"@endphp"
],
"description": "@php block code in view"
},
"includeIf": {
"prefix": "b:includeIf",
"body": "@includeIf('${1:view.name}'${2:, ['some' => 'data']})",
"description": "include a view that may or may not be present, you should use the @includeIf directive"
},
/* v5.4 */
"component": {
"prefix": "b:component",
"body": [
"@component('$1')",
" $2",
"@endcomponent"
],
"description": "component"
},
"slot": {
"prefix": "b:slot",
"body": [
"@slot('$1')",
" $2",
"@endslot"
],
"description": "slot"
},
"isset": {
"prefix": "b:isset",
"body": [
"@isset(${1:\\$record})",
" $2",
"@endisset"
],
"description": "isset"
},
"empty": {
"prefix": "b:empty",
"body": [
"@empty(${1:\\$record})",
" $2",
"@endempty"
],
"description": "empty"
},
"error": {
"prefix": "b:error",
"body": [
"@error('${1:record}')",
" $2",
"@enderror"
],
"description": "error"
},
"includeWhen": {
"prefix": "b:includeWhen",
"body": "@includeWhen(${1:\\$boolean}, '${2:view.name}', [${3:'some' => 'data'}])",
"description": "includeWhen"
},
/* v5.5 */
"auth": {
"prefix": "b:auth",
"body": [
"@auth",
" $1",
"@endauth"
],
"description": "auth"
},
"guest": {
"prefix": "b:guest",
"body": [
"@guest",
" $1",
"@endguest"
],
"description": "guest"
},
"switch": {
"prefix": "b:switch",
"body": [
"@switch(${1:\\$type})",
" @case(${2:1})",
" $3",
" @break",
" @case(${4:2})",
" $5",
" @break",
" @default",
" $6",
"@endswitch"
],
"description": "switch"
},
"includeFirst": {
"prefix": "b:includeFirst",
"body": "@includeFirst(['${1:view.name}', '${2:variable}'], [${3:'some' => 'data'}])",
"description": "includeFirst"
},
/* v5.6 */
"csrf": {
"prefix": "b:csrf",
"body": "@csrf",
"description": "form csrf field"
},
"method": {
"prefix": "b:method",
"body": "@method($1)",
"description": "form method field"
},
"dump": {
"prefix": "b:dump",
"body": "@dump($1)",
"description": "dump"
},
/* Retrieving Translation Strings */
"lang": {
"prefix": "b:lang",
"body": "@lang('${1:messages.welcome}')",
"description": "lang"
},
/* v6.x */
"includeUnless": {
"prefix": "b:includeUnless",
"body": "@includeUnless(${1:\\$boolean}, '${2:view.name}', [${3:'some' => 'data'}])",
"description": "includeUnless"
},
/* v7.x */
"props": {
"prefix": "b:props",
"body": "@props(['${1:propName}'])",
"description": "Blade component data properties"
},
"env": {
"prefix": "b:env",
"body": [
"@env('${1:staging}')",
" $2",
"@endenv"
],
"description": "env"
},
"production": {
"prefix": "b:production",
"body": [
"@production",
" $1",
"@endproduction"
],
"description": "production"
},
"once": {
"prefix": "b:once",
"body": [
"@once",
" $1",
"@endonce"
],
"description": "define a portion of template that will only be evaluated once per rendering cycle"
},
/* v8.x */
"aware": {
"prefix": "b:aware",
"body": "@aware(['${1:propName}'])",
"description": "Accessing data from a parent component (Laravel 8.64)"
},
"js": {
"prefix": "b:js",
"body": "@js(${1:\\$data})",
"description": "This directive is useful to properly escape JSON within HTML quotes"
},
"class": {
"prefix": "b:class",
"body": "@class(['${1:p-4}', ${2:'font-bold' => true}])",
"description": "conditionally compiles a CSS class string. (Laravel 8.51)"
},
/* v9.x */
"checked": {
"prefix": "b:checked",
"body": "@checked(${1:true})",
"description": "This directive will echo checked if the provided condition evaluates to true (Laravel 9.x)"
},
"selected": {
"prefix": "b:selected",
"body": "@selected(${1:true})",
"description": "The @selected directive may be used to indicate if a given select option should be \"selected\" (Laravel 9.x)"
},
"disabled": {
"prefix": "b:disabled",
"body": "@disabled(${1:true})",
"description": "The @disabled directive may be used to indicate if a given element should be \"disabled\" (Laravel 9.x)"
}
}

View file

@ -0,0 +1,87 @@
import * as path from 'path';
import * as vscode from 'vscode';
import * as html from 'vscode-html-languageservice';
import * as lst from 'vscode-languageserver-types';
import * as nls from 'vscode-nls';
import { BladeFormattingEditProvider } from './providers/BladeFormattingEditProvider';
import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind, RequestType, TextDocumentPositionParams } from 'vscode-languageclient';
const service = html.getLanguageService()
const localize = nls.loadMessageBundle();
class DocumentHighlight implements vscode.DocumentHighlightProvider
{
provideDocumentHighlights(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): vscode.DocumentHighlight[] | Thenable<vscode.DocumentHighlight[]> {
let doc = lst.TextDocument.create(document.uri.fsPath, 'html', 1, document.getText());
return (service.findDocumentHighlights(doc, position, service.parseHTMLDocument(doc)) as any);
}
} // DocumentHighlight
export function activate(context: vscode.ExtensionContext) {
let documentSelector: vscode.DocumentSelector = {
language: 'blade'
};
context.subscriptions.push(vscode.languages.registerDocumentHighlightProvider(documentSelector, new DocumentHighlight));
let bladeFormatCfg = vscode.workspace.getConfiguration('blade.format');
if (bladeFormatCfg.get<boolean>('enable')) {
context.subscriptions.push(vscode.languages.registerDocumentFormattingEditProvider(documentSelector, new BladeFormattingEditProvider));
context.subscriptions.push(vscode.languages.registerDocumentRangeFormattingEditProvider(documentSelector, new BladeFormattingEditProvider));
}
// Set html indent
const EMPTY_ELEMENTS: string[] = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr'];
vscode.languages.setLanguageConfiguration('blade', {
indentationRules: {
increaseIndentPattern: /<(?!\?|(?:area|base|br|col|frame|hr|html|img|input|link|meta|param)\b|[^>]*\/>)([-_\.A-Za-z0-9]+)(?=\s|>)\b[^>]*>(?!.*<\/\1>)|<!--(?!.*-->)|\{[^}"']*$/,
decreaseIndentPattern: /^\s*(<\/(?!html)[-_\.A-Za-z0-9]+\b[^>]*>|-->|\})/
},
wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\$\^\&\*\(\)\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\s]+)/g,
onEnterRules: [
{
beforeText: new RegExp(`<(?!(?:${EMPTY_ELEMENTS.join('|')}))([_:\\w][_:\\w-.\\d]*)([^/>]*(?!/)>)[^<]*$`, 'i'),
afterText: /^<\/([_:\w][_:\w-.\d]*)\s*>$/i,
action: { indentAction: vscode.IndentAction.IndentOutdent }
},
{
beforeText: new RegExp(`<(?!(?:${EMPTY_ELEMENTS.join('|')}))(\\w[\\w\\d]*)([^/>]*(?!/)>)[^<]*$`, 'i'),
action: { indentAction: vscode.IndentAction.Indent }
}
],
});
// The server is implemented in node
let serverModule = context.asAbsolutePath(path.join('server', 'out', 'htmlServerMain.js'));
// The debug options for the server
let debugOptions = { execArgv: ['--nolazy', '--inspect=6045'] };
// If the extension is launch in debug mode the debug server options are use
// Otherwise the run options are used
let serverOptions: ServerOptions = {
run: { module: serverModule, transport: TransportKind.ipc },
debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions }
};
let embeddedLanguages = { css: true, javascript: true };
// Options to control the language client
let clientOptions: LanguageClientOptions = {
documentSelector: ['blade'],
synchronize: {
configurationSection: ['blade', 'css', 'javascript', 'emmet'], // the settings to synchronize
},
initializationOptions: {
embeddedLanguages
}
};
// Create the language client and start the client.
let client = new LanguageClient('blade', localize('bladeserver.name', 'BLADE Language Server'), serverOptions, clientOptions);
client.registerProposedFeatures();
context.subscriptions.push(client.start());
}
export function deactivate() {
}

View file

@ -0,0 +1,42 @@
import * as vscode from 'vscode';
import * as html from 'vscode-html-languageservice';
import * as lst from 'vscode-languageserver-types';
import { BladeFormatter, IBladeFormatterOptions } from "../services/BladeFormatter";
const service = html.getLanguageService()
export class BladeFormattingEditProvider implements vscode.DocumentFormattingEditProvider, vscode.DocumentRangeFormattingEditProvider
{
formatterOptions: IBladeFormatterOptions;
provideDocumentFormattingEdits(document: vscode.TextDocument, options: vscode.FormattingOptions): vscode.TextEdit[] {
let range = new vscode.Range(new vscode.Position(0, 0), new vscode.Position((document.lineCount - 1), Number.MAX_VALUE));
return this.provideFormattingEdits(document, document.validateRange(range), options);
}
provideDocumentRangeFormattingEdits(document: vscode.TextDocument, range: vscode.Range, options: vscode.FormattingOptions): vscode.TextEdit[] {
return this.provideFormattingEdits(document, range, options);
}
private provideFormattingEdits(document: vscode.TextDocument, range: vscode.Range, options: vscode.FormattingOptions): vscode.TextEdit[] {
this.formatterOptions = {
tabSize: options.tabSize,
insertSpaces: options.insertSpaces
};
// Mapping HTML format options
let htmlFormatConfig = vscode.workspace.getConfiguration('html.format');
Object.assign(options, htmlFormatConfig);
// format as html
let doc = lst.TextDocument.create(document.uri.fsPath, 'html', 1, document.getText());
let htmlTextEdit = service.format(doc, range, options);
// format as blade
let formatter = new BladeFormatter(this.formatterOptions);
let bladeText = formatter.format(htmlTextEdit[0].newText);
return [vscode.TextEdit.replace(range, bladeText)];
}
}

View file

@ -0,0 +1,34 @@
export class BladeFormatter
{
newLine: string = "\n";
indentPattern: string;
constructor(options?: IBladeFormatterOptions) {
options = options || {};
// options default value
options.tabSize = options.tabSize || 4;
if (typeof options.insertSpaces === "undefined") {
options.insertSpaces = true;
}
this.indentPattern = (options.insertSpaces) ? " ".repeat(options.tabSize) : "\t";
}
format(inuptText: string): string {
let inComment: boolean = false;
let output: string = inuptText;
// fix #57 url extra space after formatting
output = output.replace(/url\(\"(\s*)/g, "url\(\"");
return output.trim();
}
}
export interface IBladeFormatterOptions
{
insertSpaces?: boolean;
tabSize?: number;
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,517 @@
<h1>Blade Syntax Highlighting Tests</h1>
{{-- Displaying Data --}}
Hello, {{ $name }}.
The current UNIX timestamp is {{ time() }}.
{{-- Escape Data --}}
Hello, {{{ $name }}}.
{{-- Echoing Data If It Exists --}}
{{ isset($name) ? $name : 'Default' }}
{{ $name or 'Default' }}
<div class="{{ $name }}" {{ isset($name) ? $name : 'Default' }}></div>
{{-- Displaying Unescaped Data --}}
Hello, {!! $name !!}.
{{-- Rendering JSON --}}
<script>
var app = @json($array);
var app = @json($array, JSON_PRETTY_PRINT);
</script>
{{-- Blade & JavaScript Frameworks --}}
Hello, @{{ name }}.
{{-- Blade --}}
@@json()
<!-- HTML output -->
@json()
@verbatim
<div class="container">
Hello, {{ name }}.
</div>
@endverbatim
{{-- Control Structures --}}
{{-- If Statements --}}
@if (count($records) === 1)
I have one record!
@elseif (count($records) > 1)
I have multiple records!
@else
I don't have any records!
@endif
@unless (Auth::check())
You are not signed in.
@endunless
@isset($records)
// $records is defined and is not null...
@endisset
@empty($records)
// $records is "empty"...
@endempty
{{-- Authentication Directives --}}
@auth
// The user is authenticated...
@endauth
@guest
// The user is not authenticated...
@endguest
@auth('admin')
// The user is authenticated...
@endauth
@guest('admin')
// The user is not authenticated...
@endguest
{{-- Section Directives --}}
@hasSection('navigation')
<div class="pull-right">
@yield('navigation')
</div>
<div class="clearfix"></div>
@endif
@sectionMissing('navigation')
<div class="pull-right">
@include('default-navigation')
</div>
@endif
{{-- Environment Directives --}}
@production
// Production specific content...
@endproduction
@env('staging')
// The application is running in "staging"...
@endenv
@env(['staging', 'production'])
// The application is running in "staging" or "production"...
@endenv
{{-- Section Directives --}}
@hasSection('navigation')
<div class="pull-right">
@yield('navigation')
</div>
<div class="clearfix"></div>
@endif
@sectionMissing('navigation')
<div class="pull-right">
@include('default-navigation')
</div>
@endif
{{-- Switch Statements --}}
@switch($i)
@case(1)
First case...
@break
@case(2)
Second case...
@break
@default
Default case...
@endswitch
{{-- Loops --}}
@for ($i = 0; $i < 10; $i++)
The current value is {{ $i }}
@endfor
@foreach ($users as $user)
<p>This is user {{ $user->id }}</p>
@endforeach
@forelse ($users as $user)
<li>{{ $user->name }}</li>
@empty
<p>No users</p>
@endforelse
@while (true)
<p>I'm looping forever.</p>
@endwhile
{{-- continue & break --}}
@foreach ($users as $user)
@if ($user->type == 1)
@continue
@endif
<li>{{ $user->name }}</li>
@if ($user->number == 5)
@break
@endif
@continue($user->type == 1)
<li>{{ $user->name }}</li>
@break($user->number == 5)
@endforeach
@foreach ($users as $user)
@continue($user->type == 1)
<li>{{ $user->name }}</li>
@break($user->number == 5)
@endforeach
{{-- The Loop Variable --}}
@foreach ($users as $user)
@if ($loop->first)
This is the first iteration.
@endif
@if ($loop->last)
This is the last iteration.
@endif
<p>This is user {{ $user->id }}</p>
{{-- $loop->parent --}}
@foreach ($user->posts as $post)
@if ($loop->parent->first)
This is the first iteration of the parent loop.
@endif
@endforeach
@endforeach
{{-- Loop Variables --}}
{{-- The index of the current loop iteration (starts at 0) --}}
{{ $loop->index }}
{{-- The current loop iteration (starts at 1) --}}
{{ $loop->iteration }}
{{-- The iteration remaining in the loop --}}
{{ $loop->remaining }}
{{-- The total number of items in the array being iterated --}}
{{ $loop->count }}
{{-- Whether this is the first iteration through the loop --}}
{{ $loop->first }}
{{-- Whether this is the last iteration through the loop --}}
{{ $loop->last }}
{{-- The nesting level of the current loop --}}
{{ $loop->depth }}
{{-- When in a nested loop, the parent's loop variable --}}
{{ $loop->parent }}
@endforeach
{{-- Comments --}}
{{-- This comment will not be present in the rendered HTML --}}
{{--
This comment will not be in the rendered HTML
This comment will not be in the rendered HTML
This comment will not be in the rendered HTML
--}}
{{-- PHP --}}
<?php echo $name; ?>
<?= $name; ?>
<?php echo ($var)->format('m/d/Y H:i'); ?>
<?php
foreach (range(1, 10) as $number) {
echo $number;
}
?>
@php ($hello = "hello world")
@php
foreach (range(1, 10) as $number) {
echo $number;
}
@endphp
{{-- Conditional Classes : `@class` directive --}}
@php
$isActive = false;
$hasError = true;
@endphp
<span @class([
'p-4',
'font-bold' => $isActive,
'text-gray-500' => ! $isActive,
'bg-red' => $hasError,
])></span>
<span class="p-4 text-gray-500 bg-red"></span>
{{-- The @once Directive --}}
@once
@push('scripts')
<script>
// Your custom JavaScript...
</script>
@endpush
@endonce
{{-- Forms --}}
<form method="POST" action="/foo/bar">
@csrf
@method('PUT')
</form>
{{-- Validation Errors --}}
<label for="title">Post Title</label>
<input id="title" type="text" class="@error('title') is-invalid @enderror">
@error('title')
<div class="alert alert-danger">{{ $message }}</div>
@enderror
{{-- Components --}}
<x-package-alert />
<x-nightshade::calendar />
<x-nightshade::color-picker />
<x-inputs.button />
<x-test></x-test>
<x-alert type="error" :message="$message"/>
<x-dynamic-component :component="$componentName" class="mt-4" />
{{-- Component attribute expressions --}}
<x-test
str="empty"
:null="null"
:bool-t="true"
:bool-f="false"
:num-dec-a="1234"
:num-dec-b="12_34"
:num-dec-c="0123"
:num-hex="0x1A"
:num-bin="0b101"
:num-float-a="1.234"
:num-float-b="1.2e3"
:num-float-c="7E-10"
:num-float-d="1_234.567"
:expr-math-a="true + -2 - (3 * 40) / 5 % 6 ^ '7' ** $a"
:expr-bit="$a & $b | $a ^ $b << $a >> $b"
:expr-str="1 . 'test' . '\\' . $a . true"
:expr-arr="[] + []"
:expr-func-call="func()"
:expr-func-arrow="fn($a) => $a"
:expr-cond="$a ? !$b : $c || $a ?: $b || $a ?? $b and $a or $a xor $b"
:expr-comp-a="$a == $b"
:expr-comp-b="$a === $b"
:expr-comp-c="$a != $b"
:expr-comp-d="$a <> $b"
:expr-comp-e="$a !== $b"
:expr-comp-f="$a < $b"
:expr-comp-g="$a > $b"
:expr-comp-h="$a >= $b"
:expr-comp-i="$a <= $b"
:expr-comp-j="$a <=> $b"
:expr-type="$a instanceof MyClass"
:expr-class-a="new MyClass()"
:expr-class-b="(new MyClass())->prop"
:expr-class-c="(new MyClass())->do()"
:expr-class-d="MyClass::class"
:expr-class-e="MyClass::$prop"
:expr-class-f="MyClass::do()"
:expr-class-g="$this->prop"
:expr-class-h="$this->do()"
:expr-class-i="$instance->prop"
:expr-class-j="$instance->do()"
:arr="['valueA', true, 0 => 'valueB', 'keyC' => 'valueC']"
/>
{{-- Including Sub-Views --}}
<div>
@include('shared.errors')
<form>
<!-- Form Contents -->
</form>
</div>
@include('view.name')
@include('view.name', ['some' => 'data'])
@includeIf('view.name', ['some' => 'data'])
@includeWhen($boolean, 'view.name', ['some' => 'data'])
@includeUnless($boolean, 'view.name', ['some' => 'data'])
@includeFirst(['custom.admin', 'admin'], ['some' => 'data'])
{{-- Rendering Views For Collections --}}
@each('view.name', $jobs, 'job')
@each('view.name', $jobs, 'job', 'view.empty')
{{-- Stacks --}}
@push('scripts')
<script src="/example.js"></script>
@endpush
@stack('scripts')
@prepend('scripts')
This will be first...
@endprepend
{{-- Service Injection --}}
@inject('metrics', 'App\Services\MetricsService')
<div>
Monthly Revenue: {{ $metrics->monthlyRevenue() }}.
</div>
{{-- Retrieving Translation Strings --}}
@lang('messages.welcome')
{{-- 5.3 --}}
{{ trans('messages.welcome') }}
{{-- 5.4 --}}
{{ __('messages.welcome') }}
{{-- Pluralization --}}
@choice('messages.apples', 10)
{{-- 'apples' => '{0} There are none|[1,19] There are some|[20,Inf] There are many', --}}
{{ trans_choice('messages.apples', 10) }}
{{-- Replacing Parameters In Translation Strings --}}
{{-- 'greeting' => 'Welcome, :name', --}}
{{ __('messages.greeting', ['name' => 'Winnie']) }}
{{-- Authorizing --}}
@can('update', $post)
<!-- The Current User Can Update The Post -->
@elsecan('create', App\Models\Post::class)
<!-- The Current User Can Create New Post -->
@endcan
@canany(['update'], $post)
<!-- The Current User Can Update The Post -->
@elsecanany(['create'], App\Models\Post::class)
<!-- The Current User Can Create New Post -->
@endcanany
@cannot('update', $post)
<!-- The Current User Can't Update The Post -->
@elsecannot('create', App\Models\Post::class)
<!-- The Current User Can't Create New Post -->
@endcannot
@if (Auth::user()->can('update', $post))
<!-- The Current User Can Update The Post -->
@endif
@unless (Auth::user()->can('update', $post))
<!-- The Current User Can't Update The Post -->
@endunless
@can('create', App\Models\Post::class)
<!-- The Current User Can Create Posts -->
@endcan
@canany(['create'], App\Models\Post::class)
<!-- The Current User Can Create Posts -->
@endcanany
@cannot('create', App\Models\Post::class)
<!-- The Current User Can't Create Posts -->
@endcannot
{{-- Retrieving Translation Strings --}}
{{ __('messages.welcome') }}
@lang('messages.welcome')
@props(['type' => 'info', 'message'])
{{-- Envoy --}}
@setup
require __DIR__.'/vendor/autoload.php';
$dotenv = new Dotenv\Dotenv(__DIR__);
@endsetup
@servers(['web' => $server])
@task('init')
if [ ! -d {{ $path }}/current ]; then
cd {{ $path }}
@endtask
@story('deploy')
deployment_start
deployment_composer
deployment_finish
@endstory
{{-- Livewire --}}
@livewireStyles
@livewireScripts
@livewire('user-profile', ['user' => $user], key($user->id))
{{-- Checked / Selected / Disabled Blade Directives (9.x) --}}
<input type="checkbox"
name="active"
value="active"
@checked(old('active', $user->active)) />
<select name="version">
@foreach ($product->versions as $version)
<option value="{{ $version }}" @selected(old('version') == $version)>
{{ $version }}
</option>
@endforeach
</select>
<button type="submit" @disabled($errors->isNotEmpty())>
Submit
</button>

View file

@ -0,0 +1,17 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"outDir": "out",
"lib": [
"es6"
],
"sourceMap": true,
"rootDir": ".",
"skipLibCheck": true,
"allowSyntheticDefaultImports": true
},
"exclude": [
"server"
]
}

View file

@ -0,0 +1,37 @@
//@ts-check
'use strict';
const path = require('path');
/**@type {import('webpack').Configuration}*/
const config = {
target: 'node',
entry: './src/extension.ts',
output: {
path: path.resolve(__dirname, 'out'),
filename: 'extension.js',
libraryTarget: 'commonjs2'
},
devtool: 'source-map',
externals: {
vscode: 'commonjs vscode'
},
resolve: {
extensions: ['.ts', '.js']
},
module: {
rules: [
{
test: /\.ts$/,
exclude: /node_modules/,
use: [
{
loader: 'ts-loader'
}
]
}
]
}
};
module.exports = config;