├── logo.png
├── .gitignore
├── .vscodeignore
├── tsconfig.json
├── .vscode
├── tasks.json
└── launch.json
├── download-phar.js
├── src
├── index.d.ts
├── output.ts
├── runAsync.ts
├── beautifyHtml.ts
└── index.ts
├── .github
└── workflows
│ └── publish.yml
├── LICENSE.txt
├── package.json
└── README.md
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/junstyle/vscode-php-cs-fixer/HEAD/logo.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .eslint*
3 | php-cs-fixer.phar
4 | *.vsix
5 | extension_pack.js
6 | extension_pack.js.map
7 | *.js.map
8 | tests
9 |
--------------------------------------------------------------------------------
/.vscodeignore:
--------------------------------------------------------------------------------
1 | .vscode/**
2 | .github/**
3 | .gitignore
4 | .eslintrc.json
5 | node_modules/
6 | src/
7 | jsconfig.json
8 | tsconfig.json
9 | download-phar.js
10 | package-lock.json
11 | pnpm-lock.yaml
12 | yarn.lock
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es2020",
5 | "lib": ["es2020"],
6 | "outDir": "out",
7 | "rootDir": "src",
8 | "sourceMap": true
9 | },
10 | "include": ["src"],
11 | "exclude": ["node_modules", ".vscode-test"]
12 | }
13 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "type": "npm",
6 | "script": "watch",
7 | "group": "build",
8 | "problemMatcher": "$esbuild-watch",
9 | "isBackground": true,
10 | "label": "npm: watch"
11 | },
12 | {
13 | "type": "npm",
14 | "script": "build",
15 | "group": "build",
16 | "problemMatcher": "$esbuild",
17 | "label": "npm: build"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/download-phar.js:
--------------------------------------------------------------------------------
1 | const { DownloaderHelper } = require('node-downloader-helper')
2 | const dl = new DownloaderHelper('https://cs.symfony.com/download/php-cs-fixer-v3.phar', __dirname, { fileName: 'php-cs-fixer.phar', override: true })
3 | dl.on('start', () => console.log('start to download php-cs-fixer.phar'))
4 | dl.on('end', () => console.log('download php-cs-fixer.phar successfully.'))
5 | // dl.on('progress', stat => {
6 | // console.log('\rdownloading php-cs-fixer.phar: ' + Math.floor(stat.progress) + '%');
7 | // });
8 | dl.start()
9 |
--------------------------------------------------------------------------------
/src/index.d.ts:
--------------------------------------------------------------------------------
1 | export class PHPCSFixerConfig {
2 | onsave: boolean
3 | autoFixByBracket: boolean
4 | autoFixBySemicolon: boolean
5 | executablePath: string
6 | rules: string | Object
7 | config: string
8 | formatHtml: boolean
9 | documentFormattingProvider: boolean
10 | allowRisky: boolean
11 | pathMode: 'pathMode' | 'override'
12 | ignorePHPVersion: boolean
13 | exclude: string[]
14 | pharPath: string
15 | editorFormatOnSave: boolean
16 | // fileAutoSave: boolean
17 | // fileAutoSaveDelay: number
18 | tmpDir: string
19 | }
20 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: publish
2 |
3 | on:
4 | push:
5 | branches: ['master']
6 | # pull_request:
7 | # branches: [ "master" ]
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - uses: actions/checkout@v3
15 | with:
16 | fetch-depth: 0
17 |
18 | - uses: actions/setup-node@v3
19 | with:
20 | node-version: 20
21 | # cache: 'npm'
22 |
23 | - run: npm install
24 | - run: npm install -g vsce esbuild
25 | - run: node ./download-phar.js
26 | # - run: esbuild ./src/index.ts --bundle --minify --outfile=./index.js --external:vscode --format=cjs --platform=node
27 | - run: vsce publish -p ${{ secrets.VSC_TOKEN }}
28 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | // A launch configuration that compiles the extension and then opens it inside a new window
2 | {
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "Run Extension",
7 | "type": "extensionHost",
8 | "request": "launch",
9 | "args": ["--extensionDevelopmentPath=${workspaceFolder}"],
10 | "outFiles": ["${workspaceFolder}/**/*.js"],
11 | "preLaunchTask": "npm: watch"
12 | },
13 | {
14 | "name": "Extension Tests",
15 | "type": "extensionHost",
16 | "request": "launch",
17 | "args": ["--extensionDevelopmentPath=${workspaceFolder}", "--extensionTestsPath=${workspaceFolder}/out/test/suite/index"],
18 | "outFiles": ["${workspaceFolder}/out/test/**/*.js"],
19 | "preLaunchTask": "${defaultBuildTask}"
20 | }
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 junstyle
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/output.ts:
--------------------------------------------------------------------------------
1 | import { window, StatusBarAlignment } from 'vscode'
2 |
3 | let outputChannel = null
4 | let statusBarItem = null
5 |
6 | function createOutput() {
7 | if (outputChannel == null) {
8 | outputChannel = window.createOutputChannel('php-cs-fixer')
9 | }
10 | }
11 |
12 | export function output(str) {
13 | createOutput()
14 | outputChannel.appendLine(str)
15 | }
16 |
17 | export function showOutput() {
18 | createOutput()
19 | outputChannel.show(true)
20 | }
21 |
22 | export function clearOutput() {
23 | outputChannel?.clear()
24 | }
25 |
26 | export function statusInfo(str) {
27 | if (statusBarItem == null) {
28 | statusBarItem = window.createStatusBarItem(StatusBarAlignment.Left, -10000000)
29 | statusBarItem.command = 'php-cs-fixer.showOutput'
30 | statusBarItem.tooltip = 'php-cs-fixer: show output'
31 | }
32 |
33 | statusBarItem.show()
34 | statusBarItem.text = 'php-cs-fixer: ' + str
35 | }
36 |
37 | export function hideStatusBar() {
38 | statusBarItem?.hide()
39 | }
40 |
41 | export function disposeOutput() {
42 | if (outputChannel) {
43 | outputChannel.clear()
44 | outputChannel.dispose()
45 | }
46 | if (statusBarItem) {
47 | statusBarItem.hide()
48 | statusBarItem.dispose()
49 | }
50 | outputChannel = null
51 | statusBarItem = null
52 | }
53 |
--------------------------------------------------------------------------------
/src/runAsync.ts:
--------------------------------------------------------------------------------
1 | import { spawn, SpawnOptionsWithoutStdio } from 'child_process';
2 | import { output } from './output';
3 |
4 | export function runAsync(command: string, args: string[], options: SpawnOptionsWithoutStdio, onData: (data: Buffer) => void = null) {
5 | const cpOptions = Object.assign({}, options, { shell: process.platform == 'win32' })
6 | let cp;
7 | try {
8 | if (process.platform == 'win32') {
9 | if (command.includes(" ") && command[0] != '"') {
10 | command = '"' + command + '"'
11 | }
12 | }
13 |
14 | output('runAsync: spawn ' + command);
15 | output(JSON.stringify(args, null, 2))
16 | output(JSON.stringify(cpOptions, null, 2))
17 |
18 | cp = spawn(command, args, cpOptions)
19 | } catch (err) {
20 | const promise = new Promise((resolve, reject) => {
21 | output('runAsync: error')
22 | output(JSON.stringify(err, null, 2))
23 | output('runAsync: reject promise')
24 | reject(err)
25 | })
26 | ; (promise as any).cp = cp
27 |
28 | return promise
29 | }
30 |
31 | const promise = new Promise((resolve, reject) => {
32 | let stdout = null
33 | let stderr = null
34 |
35 | cp.stdout &&
36 | cp.stdout.on('data', (data) => {
37 | stdout = stdout || Buffer.from('')
38 | stdout = Buffer.concat([stdout, data])
39 | onData && onData(data)
40 | })
41 |
42 | cp.stderr &&
43 | cp.stderr.on('data', (data) => {
44 | stderr = stderr || Buffer.from('')
45 | stderr = Buffer.concat([stderr, data])
46 | // stdout = stdout || Buffer.from('');
47 | // stdout = Buffer.concat([stdout, data]);
48 | onData && onData(data)
49 | })
50 |
51 | const cleanupListeners = () => {
52 | cp.removeListener('error', onError)
53 | cp.removeListener('close', onClose)
54 | }
55 |
56 | const onError = (err) => {
57 | cleanupListeners()
58 |
59 | output('runAsync: error')
60 | output(JSON.stringify(err, null, 2))
61 | output('runAsync: reject promise')
62 | reject(err)
63 | }
64 |
65 | const onClose = (code) => {
66 | cleanupListeners()
67 |
68 | const resolved = resolveRun(code, stdout, stderr)
69 |
70 | if (resolved instanceof Error) {
71 | output('runAsync: error')
72 | output(JSON.stringify(resolved, null, 2))
73 | output('runAsync: reject promise')
74 | reject(resolved)
75 | } else {
76 | output('runAsync: success')
77 | output(JSON.stringify(resolved, null, 2))
78 | output('runAsync: resolve promise')
79 | resolve(resolved)
80 | }
81 | }
82 |
83 | cp.on('error', onError).on('close', onClose)
84 | })
85 |
86 | ; (promise as any).cp = cp
87 |
88 | return promise
89 | }
90 |
91 | function resolveRun(exitCode, stdout, stderr) {
92 | stdout = stdout && stdout.toString()
93 | stderr = stderr && stderr.toString()
94 |
95 | if (exitCode !== 0) {
96 | return Object.assign(new Error(`Command failed, exited with code #${exitCode}`), {
97 | exitCode,
98 | stdout,
99 | stderr,
100 | })
101 | }
102 |
103 | return {
104 | stdout,
105 | stderr,
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/beautifyHtml.ts:
--------------------------------------------------------------------------------
1 | const beautifyHtml = require('js-beautify').html
2 | const phpParser = require('php-parser')
3 | const htmlparser = require('htmlparser2')
4 |
5 | function getFormatOption(options, key, dflt) {
6 | if (options && Object.prototype.hasOwnProperty.call(options, key)) {
7 | let value = options[key]
8 | if (value !== null) {
9 | return value
10 | }
11 | }
12 | return dflt
13 | }
14 |
15 | function getTagsFormatOption(options, key, dflt) {
16 | let list = getFormatOption(options, key, null)
17 | if (typeof list === 'string') {
18 | if (list.length > 0) {
19 | return list.split(',').map((t) => t.trim().toLowerCase())
20 | }
21 | return []
22 | }
23 | return dflt
24 | }
25 |
26 | function preAction(php: string): string {
27 | let scriptStyleRanges = getScriptStyleRanges(php)
28 | let strArr = []
29 | let tokens = new phpParser().tokenGetAll(php)
30 | let c = tokens.length
31 | let index = 0
32 | for (let i = 0; i < c; i++) {
33 | let t = tokens[i]
34 | if (inScriptStyleTag(scriptStyleRanges, index)) {
35 | if (typeof t == 'object') {
36 | if (t[0] == 'T_OPEN_TAG' || t[0] == 'T_OPEN_TAG_WITH_ECHO') {
37 | strArr.push('/*%pcs-comment-start#' + t[1])
38 | } else if (t[0] == 'T_CLOSE_TAG') {
39 | // fix new line issue
40 | let ms = t[1].match(/(\S+)(\s+)$/)
41 | if (ms) {
42 | strArr.push(ms[1] + '%pcs-comment-end#*/' + ms[2])
43 | } else {
44 | strArr.push(t[1] + '%pcs-comment-end#*/')
45 | }
46 | } else {
47 | if (t[0] == 'T_INLINE_HTML') {
48 | strArr.push(t[1])
49 | } else {
50 | let str = t[1].replace(/\*\//g, '*%comment-end#/').replace(/"/g, 'pcs%quote#1').replace(/'/g, 'pcs%quote~2')
51 | strArr.push(str)
52 | }
53 | }
54 | index += t[1].length
55 | } else {
56 | strArr.push(t)
57 | index += t.length
58 | }
59 | } else {
60 | if (typeof t == 'object') {
61 | if (t[0] == 'T_OPEN_TAG' || t[0] == 'T_OPEN_TAG_WITH_ECHO') {
62 | // ' + ms[2])
69 | } else {
70 | strArr.push(t[1] + '%pcs-comment-end#-->')
71 | }
72 | } else {
73 | if (t[0] == 'T_INLINE_HTML') {
74 | strArr.push(t[1])
75 | } else {
76 | let str = t[1].replace(/-->/g, '-%comment-end#->').replace(/"/g, 'pcs%quote#1').replace(/'/g, 'pcs%quote~2')
77 | strArr.push(str)
78 | }
79 | }
80 | index += t[1].length
81 | } else {
82 | strArr.push(t)
83 | index += t.length
84 | }
85 | }
86 | }
87 | if (typeof tokens[c - 1] == 'object' && tokens[c - 1][0] != 'T_CLOSE_TAG' && tokens[c - 1][0] != 'T_INLINE_HTML') {
88 | strArr.push('?>%pcs-comment-end#-->')
89 | }
90 | return strArr.join('')
91 | }
92 |
93 | function afterAction(php: string): string {
94 | return php
95 | // .replace(/\?>\s*%pcs-comment-end#-->\s*$/g, '')
96 | .replace(/%pcs-comment-end#-->/g, '')
97 | .replace(/\s*<\/i>\s*')
99 | .replace(/%pcs-comment-end#\*\//g, '')
100 | .replace(/\/\*%pcs-comment-start#/g, '')
101 | .replace(/\*%comment-end#\//g, '*/')
102 | .replace(/pcs%quote#1/g, '"')
103 | .replace(/pcs%quote~2/g, "'")
104 | }
105 |
106 | /**
107 | * get all script/style tag ranges
108 | * @param {php code} php
109 | */
110 | function getScriptStyleRanges(php) {
111 | let ranges = []
112 | let start = 0
113 | let parser = new htmlparser.Parser(
114 | {
115 | onopentag: (name) => {
116 | if (name === 'script' || name === 'style') {
117 | start = parser.startIndex
118 | }
119 | },
120 | onclosetag: (name) => {
121 | if (name === 'script' || name === 'style') {
122 | ranges.push([start, parser.endIndex])
123 | }
124 | },
125 | },
126 | {
127 | decodeEntities: true,
128 | }
129 | )
130 | parser.write(php)
131 | parser.end()
132 | return ranges
133 | }
134 |
135 | function inScriptStyleTag(ranges: number[], index: number) {
136 | for (let i = 0, c = ranges.length; i < c; i++) {
137 | if (index >= ranges[i][0] && index <= ranges[i][1]) {
138 | return true
139 | }
140 | }
141 | return false
142 | }
143 |
144 | export function beautify(text: string, options: any): string {
145 | //if only php code, return text directly
146 | let indexOfPhp = text.indexOf('')
148 | if (indexOfPhp > -1 && indexOfPhp == text.lastIndexOf('')) {
149 | if (indexOfEndPhp == -1 || indexOfEndPhp + 3 == text.length) {
150 | return text.replace(/^\s+<\?php/i, ' `php-cs-fixer: fix this file`
12 |
13 | or keyboard shortcut `alt+shift+f` vs code default formatter shortcut
14 |
15 | or right mouse context menu `Format Document`
16 |
17 | or right mouse context menu `Format Selection`
18 |
19 | or right mouse context menu on explorer `php-cs-fixer: fix`
20 |
21 | ## Install php-cs-fixer
22 |
23 | 1. this extension has included `php-cs-fixer.phar` for beginner, maybe performance lower.
24 |
25 | 2. if you want to install php-cs-fixer by yourself, see: [php-cs-fixer Installation guide](https://github.com/FriendsOfPHP/PHP-CS-Fixer#installation)
26 |
27 | ## Configuration
28 |
29 | ```JSON5
30 | {
31 | "php-cs-fixer.executablePath": "php-cs-fixer",
32 | "php-cs-fixer.executablePathWindows": "", //eg: php-cs-fixer.bat
33 | "php-cs-fixer.onsave": false,
34 | "php-cs-fixer.rules": "@PSR12",
35 | "php-cs-fixer.config": ".php-cs-fixer.php;.php-cs-fixer.dist.php;.php_cs;.php_cs.dist",
36 | "php-cs-fixer.allowRisky": false,
37 | "php-cs-fixer.pathMode": "override",
38 | "php-cs-fixer.ignorePHPVersion": false,
39 | "php-cs-fixer.exclude": [],
40 | "php-cs-fixer.autoFixByBracket": false,
41 | "php-cs-fixer.autoFixBySemicolon": false,
42 | "php-cs-fixer.formatHtml": false,
43 | "php-cs-fixer.documentFormattingProvider": true
44 | }
45 | ```
46 |
47 | TIPS: If you are using other formatters like Prettier, you can get some conflicts with auto save or other features. To fix it, you have to add this line:
48 |
49 | ```JSON5
50 | // Set PHP CS Fixer as the default formatter for PHP files
51 | // It will avoid conflict with other formatters like prettier etc.
52 | "[php]": {
53 | "editor.defaultFormatter": "junstyle.php-cs-fixer"
54 | },
55 | ```
56 |
57 | install php-cs-fixer by composer
58 |
59 | ```JSON
60 | "php-cs-fixer.executablePath": "php-cs-fixer"
61 | ```
62 |
63 | **TIP:** try "php-cs-fixer.bat" on **Windows**.
64 |
65 | or use phar file
66 |
67 | ```JSON
68 | "php-cs-fixer.executablePath": "/full/path/of/php-cs-fixer.phar"
69 | ```
70 |
71 | You also have `executablePathWindows` available if you want to specify Windows specific path. Useful if you share your workspace settings among different environments.
72 |
73 | executablePath can use ${workspaceFolder} as workspace first root folder path.
74 |
75 | [executablePath, executablePathWindows, config] can use "~/" as user home directory on os.
76 |
77 | Additionally you can configure this extension to execute on save.
78 |
79 | ```JSON
80 | "php-cs-fixer.onsave": true
81 | ```
82 |
83 | you can format html at the same time.
84 |
85 | ```JSON
86 | "php-cs-fixer.formatHtml": true
87 | ```
88 |
89 | You can use a config file from a list of semicolon separated values
90 |
91 | ```JSON
92 | "php-cs-fixer.config": ".php-cs-fixer.php;.php-cs-fixer.dist.php;.php_cs;.php_cs.dist"
93 | ```
94 |
95 | config file can place in workspace root folder or .vscode folder or any other folders:
96 |
97 | ```JSON
98 | "php-cs-fixer.config": "/full/config/file/path"
99 | ```
100 |
101 | Relative paths are only considered when a workspace folder is open.
102 |
103 | config file .php-cs-fixer.php example
104 |
105 | ```php
106 | setRules([
110 | '@PSR12' => true,
111 | 'array_indentation' => true,
112 | 'array_syntax' => ['syntax' => 'short'],
113 | 'combine_consecutive_unsets' => true,
114 | 'class_attributes_separation' => ['elements' => ['method' => 'one',]],
115 | 'multiline_whitespace_before_semicolons' => false,
116 | 'single_quote' => true,
117 |
118 | 'binary_operator_spaces' => [
119 | 'operators' => [
120 | // '=>' => 'align',
121 | // '=' => 'align'
122 | ]
123 | ],
124 | // 'blank_line_after_opening_tag' => true,
125 | // 'blank_line_before_statement' => true,
126 | 'braces' => [
127 | 'allow_single_line_closure' => true,
128 | ],
129 | // 'cast_spaces' => true,
130 | // 'class_definition' => array('singleLine' => true),
131 | 'concat_space' => ['spacing' => 'one'],
132 | 'declare_equal_normalize' => true,
133 | 'function_typehint_space' => true,
134 | 'single_line_comment_style' => ['comment_types' => ['hash']],
135 | 'include' => true,
136 | 'lowercase_cast' => true,
137 | // 'native_function_casing' => true,
138 | // 'new_with_braces' => true,
139 | // 'no_blank_lines_after_class_opening' => true,
140 | // 'no_blank_lines_after_phpdoc' => true,
141 | 'no_blank_lines_before_namespace' => true,
142 | // 'no_empty_comment' => true,
143 | // 'no_empty_phpdoc' => true,
144 | // 'no_empty_statement' => true,
145 | 'no_extra_blank_lines' => [
146 | 'tokens' => [
147 | 'curly_brace_block',
148 | 'extra',
149 | // 'parenthesis_brace_block',
150 | // 'square_brace_block',
151 | 'throw',
152 | 'use',
153 | ]
154 | ],
155 | // 'no_leading_import_slash' => true,
156 | // 'no_leading_namespace_whitespace' => true,
157 | // 'no_mixed_echo_print' => array('use' => 'echo'),
158 | 'no_multiline_whitespace_around_double_arrow' => true,
159 | // 'no_short_bool_cast' => true,
160 | // 'no_singleline_whitespace_before_semicolons' => true,
161 | 'no_spaces_around_offset' => true,
162 | // 'no_trailing_comma_in_list_call' => true,
163 | // 'no_trailing_comma_in_singleline_array' => true,
164 | // 'no_unneeded_control_parentheses' => true,
165 | // 'no_unused_imports' => true,
166 | 'no_whitespace_before_comma_in_array' => true,
167 | 'no_whitespace_in_blank_line' => true,
168 | // 'normalize_index_brace' => true,
169 | 'object_operator_without_whitespace' => true,
170 | // 'php_unit_fqcn_annotation' => true,
171 | // 'phpdoc_align' => true,
172 | // 'phpdoc_annotation_without_dot' => true,
173 | // 'phpdoc_indent' => true,
174 | // 'phpdoc_inline_tag' => true,
175 | // 'phpdoc_no_access' => true,
176 | // 'phpdoc_no_alias_tag' => true,
177 | // 'phpdoc_no_empty_return' => true,
178 | // 'phpdoc_no_package' => true,
179 | // 'phpdoc_no_useless_inheritdoc' => true,
180 | // 'phpdoc_return_self_reference' => true,
181 | // 'phpdoc_scalar' => true,
182 | // 'phpdoc_separation' => true,
183 | // 'phpdoc_single_line_var_spacing' => true,
184 | // 'phpdoc_summary' => true,
185 | // 'phpdoc_to_comment' => true,
186 | // 'phpdoc_trim' => true,
187 | // 'phpdoc_types' => true,
188 | // 'phpdoc_var_without_name' => true,
189 | // 'increment_style' => true,
190 | // 'return_type_declaration' => true,
191 | // 'self_accessor' => true,
192 | // 'short_scalar_cast' => true,
193 | // 'single_blank_line_before_namespace' => true,
194 | // 'single_class_element_per_statement' => true,
195 | // 'space_after_semicolon' => true,
196 | // 'standardize_not_equals' => true,
197 | 'ternary_operator_spaces' => true,
198 | // 'trailing_comma_in_multiline' => ['elements' => ['arrays']],
199 | 'trim_array_spaces' => true,
200 | 'unary_operator_spaces' => true,
201 | 'whitespace_after_comma_in_array' => true,
202 | 'space_after_semicolon' => true,
203 | // 'single_blank_line_at_eof' => false
204 | ])
205 | // ->setIndent("\t")
206 | ->setLineEnding("\n")
207 | ;
208 | ```
209 |
210 | If you have installed other PHP related extensions to VS Code it may happen that
211 | another formatter is used per default. You can force this extension to be used
212 | per default by adding this to your settings:
213 |
214 | ```JSON
215 | "[php]": {
216 | "editor.defaultFormatter": "junstyle.php-cs-fixer"
217 | }
218 | ```
219 |
220 | ## Auto fix
221 |
222 | ```text
223 | 1. by Bracket, when press down the key } auto fix the code in the brackets {}
224 | 2. by Semicolon, when press down the key ; auto fix the code at the current line
225 | ```
226 |
227 | For more information please visit: [https://github.com/FriendsOfPHP/PHP-CS-Fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer)
228 |
229 | ## License
230 |
231 | MIT
232 |
233 | ## If you love this extension
234 |
235 | [
](https://www.buymeacoffee.com/junstyle)
236 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | import anymatch from 'anymatch'
3 | import { SpawnOptionsWithoutStdio } from 'child_process'
4 | import * as fs from 'fs'
5 | import * as os from 'os'
6 | import * as path from 'path'
7 | import { commands, ExtensionContext, FormattingOptions, languages, Position, Range, TextDocument, TextDocumentChangeEvent, TextEdit, Uri, window, workspace, WorkspaceFolder } from 'vscode'
8 | import { beautify } from './beautifyHtml'
9 | import { PHPCSFixerConfig } from './index.d'
10 | import { clearOutput, disposeOutput, hideStatusBar, output, showOutput, statusInfo } from './output'
11 | import { runAsync } from './runAsync'
12 | const TmpDir = os.tmpdir()
13 | const HomeDir = os.homedir()
14 | let isRunning = false
15 | let lastActiveEditor = null
16 |
17 | class PHPCSFixer extends PHPCSFixerConfig {
18 | constructor() {
19 | super()
20 | this.loadSettings()
21 | this.checkUpdate()
22 | }
23 |
24 | loadSettings() {
25 | const config = workspace.getConfiguration('php-cs-fixer')
26 | this.onsave = config.get('onsave', false)
27 | this.autoFixByBracket = config.get('autoFixByBracket', true)
28 | this.autoFixBySemicolon = config.get('autoFixBySemicolon', false)
29 | this.executablePath = config.get('executablePath', process.platform === 'win32' ? 'php-cs-fixer.bat' : 'php-cs-fixer')
30 | if (process.platform == 'win32' && config.get('executablePathWindows', '').length > 0) {
31 | this.executablePath = config.get('executablePathWindows')
32 | }
33 | this.executablePath = this.resolveVscodeExpressions(this.executablePath)
34 | this.rules = config.get('rules', '@PSR12')
35 | if (typeof this.rules == 'object') {
36 | this.rules = JSON.stringify(this.rules)
37 | }
38 | this.config = config.get('config', '.php-cs-fixer.php;.php-cs-fixer.dist.php;.php_cs;.php_cs.dist')
39 | this.formatHtml = config.get('formatHtml', false)
40 | this.documentFormattingProvider = config.get('documentFormattingProvider', true)
41 | this.allowRisky = config.get('allowRisky', false)
42 | this.pathMode = config.get('pathMode', 'override')
43 | this.ignorePHPVersion = config.get('ignorePHPVersion', false)
44 | this.exclude = config.get('exclude', [])
45 | this.tmpDir = config.get('tmpDir', '')
46 |
47 | if (this.executablePath.endsWith('.phar')) {
48 | this.pharPath = this.executablePath.replace(/^php[^ ]* /i, '')
49 | this.executablePath = workspace.getConfiguration('php').get('validate.executablePath', 'php')
50 | if (!this.executablePath) {
51 | this.executablePath = 'php'
52 | }
53 | } else {
54 | this.pharPath = null
55 | }
56 |
57 | this.editorFormatOnSave = workspace.getConfiguration('editor').get('formatOnSave')
58 | // this.fileAutoSave = workspace.getConfiguration('files').get('autoSave')
59 | // this.fileAutoSaveDelay = workspace.getConfiguration('files').get('autoSaveDelay', 1000)
60 | }
61 |
62 | /**
63 | * Gets the workspace folder containing the given uri or `null` if no
64 | * workspace folder contains it and it cannot be reasonably inferred.
65 | */
66 | getActiveWorkspaceFolder(uri: Uri): WorkspaceFolder | undefined {
67 | let candidate = workspace.getWorkspaceFolder(uri)
68 |
69 | // Fallback to using the single root workspace's folder. Multi-root
70 | // workspaces should not be used because its impossible to guess which one
71 | // the developer intended to use.
72 | if (candidate === undefined && workspace.workspaceFolders?.length === 1) {
73 | candidate = workspace.workspaceFolders[0]
74 | }
75 |
76 | return candidate
77 | }
78 |
79 | /**
80 | * Resolves and interpolates vscode expressions in a given string.
81 | *
82 | * Supports the following expressions:
83 | * - "${workspaceFolder}" or "${workspaceRoot}" (deprecated). Resolves to the
84 | * workspace folder that contains the given `context.uri`.
85 | * - "${extensionPath}" Resolves to the root folder of this extension.
86 | * - "~" Resolves to the user's home directory.
87 | *
88 | * @param context Any additional context that may be necessary to resolve
89 | * expressions. Expressions with missing context are left as is.
90 | */
91 | resolveVscodeExpressions(input: string, context: { uri?: Uri } = {}) {
92 | const pattern = /^\$\{workspace(Root|Folder)\}/
93 | if (pattern.test(input) && context.uri) {
94 | const workspaceFolder = this.getActiveWorkspaceFolder(context.uri)
95 | // As of time of writing only workspace folders on disk are supported
96 | // since the php-cs-fixer binary expects to work off local files. UNC
97 | // filepaths may be supported but this is untested.
98 | if (workspaceFolder != null && workspaceFolder.uri.scheme === 'file') {
99 | input = input.replace(pattern, workspaceFolder.uri.fsPath)
100 | }
101 | }
102 |
103 | input = input.replace('${extensionPath}', __dirname)
104 | input = input.replace(/^~\//, os.homedir() + '/')
105 |
106 | return path.normalize(input)
107 | }
108 |
109 | getRealExecutablePath(uri: Uri): string | undefined {
110 | return this.resolveVscodeExpressions(this.executablePath, { uri })
111 | }
112 |
113 | getArgs(uri: Uri, filePath: string = null): string[] {
114 | filePath = filePath || uri.fsPath
115 |
116 | let args = ['fix', '--using-cache=no', '--format=json']
117 | if (this.pharPath != null) {
118 | args.unshift(this.resolveVscodeExpressions(this.pharPath, { uri }))
119 | }
120 | let useConfig = false
121 | if (this.config.length > 0) {
122 | let rootUri = this.getActiveWorkspaceFolder(uri)?.uri
123 | let configFiles = this.config
124 | .split(';') // allow multiple files definitions semicolon separated values
125 | .filter((file) => '' !== file) // do not include empty definitions
126 | .map((file) => file.replace(/^~\//, os.homedir() + '/')) // replace ~/ with home dir
127 |
128 | // include also {workspace.rootUri}/.vscode/ & {workspace.rootUri}/
129 | let searchUris = []
130 | if (rootUri != null && rootUri.scheme === 'file') {
131 | searchUris = [Uri.joinPath(rootUri, '.vscode'), rootUri]
132 | }
133 |
134 | const files = []
135 | for (const file of configFiles) {
136 | if (path.isAbsolute(file)) {
137 | files.push(file)
138 | } else {
139 | for (const searchUri of searchUris) {
140 | files.push(Uri.joinPath(searchUri, file).fsPath)
141 | }
142 | }
143 | }
144 |
145 | for (let i = 0, len = files.length; i < len; i++) {
146 | let c = files[i]
147 | if (fs.existsSync(c)) {
148 | if (process.platform == 'win32') {
149 | args.push('--config="' + c.replace(/"/g, "\\\"") + '"')
150 | } else {
151 | args.push('--config=' + c)
152 | }
153 | useConfig = true
154 | break
155 | }
156 | }
157 | }
158 | if (!useConfig && this.rules) {
159 | if (process.platform == 'win32') {
160 | args.push('--rules="' + (this.rules as string).replace(/"/g, "\\\"") + '"')
161 | } else {
162 | args.push('--rules=' + this.rules)
163 | }
164 | }
165 | if (this.allowRisky) {
166 | args.push('--allow-risky=yes')
167 | }
168 |
169 | if (filePath.startsWith(TmpDir)) {
170 | args.push('--path-mode=override')
171 | } else {
172 | args.push('--path-mode=' + this.pathMode)
173 | }
174 | args.push(filePath)
175 |
176 | return args
177 | }
178 |
179 | format(text: string | Buffer, uri: Uri, isDiff: boolean = false, isPartial: boolean = false): Promise {
180 | isRunning = true
181 | clearOutput()
182 | isPartial || statusInfo('formatting')
183 |
184 | let filePath: string
185 | // if interval between two operations too short, see: https://github.com/junstyle/vscode-php-cs-fixer/issues/76
186 | // so set different filePath for partial codes;
187 | if (isPartial) {
188 | filePath = TmpDir + '/php-cs-fixer-partial.php'
189 | } else {
190 | let tmpDirs = [this.tmpDir, TmpDir, HomeDir].filter(Boolean);
191 | for (let i = 0; i < tmpDirs.length; i++) {
192 | filePath = path.join(tmpDirs[i], 'pcf-tmp' + Math.random(), uri.fsPath.replace(/^.*[\\/]/, ''))
193 | try {
194 | fs.mkdirSync(path.dirname(filePath), { recursive: true })
195 | this.tmpDir = tmpDirs[i]
196 | break;
197 | } catch (err) {
198 | console.error(err)
199 | filePath = ''
200 | }
201 | }
202 | if (!filePath) {
203 | statusInfo("can't make tmp dir, please check the php-cs-fixer settings, set a writable dir to tmpDir.")
204 | return Promise.reject()
205 | }
206 | }
207 |
208 | fs.writeFileSync(filePath, text)
209 |
210 | const args = this.getArgs(uri, filePath)
211 | const opts: SpawnOptionsWithoutStdio = {}
212 | if (uri.scheme == 'file') {
213 | opts.cwd = path.dirname(uri.fsPath)
214 | }
215 | if (this.ignorePHPVersion) {
216 | opts.env = Object.create(process.env)
217 | opts.env.PHP_CS_FIXER_IGNORE_ENV = "1"
218 | }
219 |
220 | return new Promise((resolve, reject) => {
221 | runAsync(this.getRealExecutablePath(uri), args, opts)
222 | .then(({ stdout, stderr }) => {
223 | output(stdout)
224 |
225 | if (isDiff) {
226 | resolve(filePath)
227 | } else {
228 | let result = JSON.parse(stdout)
229 | if (result && result.files.length > 0) {
230 | resolve(fs.readFileSync(filePath, 'utf-8'))
231 | } else {
232 | let lines = stderr.split(/\r?\n/).filter(Boolean)
233 | if (lines.length > 1) {
234 | output(stderr)
235 | isPartial || statusInfo(lines[1])
236 | return reject(new Error(stderr))
237 | } else {
238 | resolve(text.toString())
239 | }
240 | }
241 | }
242 | hideStatusBar()
243 | })
244 | .catch((err) => {
245 | reject(err)
246 | output(err.stderr || JSON.stringify(err, null, 2))
247 | isPartial || statusInfo('failed')
248 |
249 | if (err.code == 'ENOENT') {
250 | this.errorTip()
251 | } else if (err.exitCode) {
252 | const msgs = {
253 | 1: err.stdout || 'General error (or PHP minimal requirement not matched).',
254 | 16: 'Configuration error of the application.', // The path "/file/path.php" is not readable
255 | 32: 'Configuration error of a Fixer.',
256 | 64: 'Exception raised within the application.',
257 | 255: err.stderr?.match(/PHP (?:Fatal|Parse) error:\s*Uncaught Error:[^\r?\n]+/)?.[0] || 'PHP Fatal error, click to show output.',
258 | }
259 | isPartial || statusInfo(msgs[err.exitCode])
260 | }
261 | })
262 | .finally(() => {
263 | isRunning = false
264 | if (!isDiff && !isPartial) {
265 | fs.rm(path.dirname(filePath), { recursive: true, force: true }, function (err) { err && console.error(err) })
266 | }
267 | })
268 | })
269 | }
270 |
271 | fix(uri: Uri) {
272 | isRunning = true
273 | clearOutput()
274 | statusInfo('fixing')
275 |
276 | const args = this.getArgs(uri)
277 | const opts: SpawnOptionsWithoutStdio = {}
278 | if (uri.fsPath != '') {
279 | opts.cwd = path.dirname(uri.fsPath)
280 | }
281 | if (this.ignorePHPVersion) {
282 | opts.env = Object.create(process.env)
283 | opts.env.PHP_CS_FIXER_IGNORE_ENV = "1"
284 | }
285 |
286 | runAsync(this.getRealExecutablePath(uri), args, opts, (data) => {
287 | output(data.toString())
288 | })
289 | .then(({ stdout }) => {
290 | hideStatusBar()
291 | })
292 | .catch((err) => {
293 | statusInfo('failed')
294 | if (err.code == 'ENOENT') {
295 | this.errorTip()
296 | }
297 | })
298 | .finally(() => {
299 | isRunning = false
300 | })
301 | }
302 |
303 | diff(uri: Uri) {
304 | this.format(fs.readFileSync(uri.fsPath), uri, true)
305 | .then((tempFilePath) => {
306 | commands.executeCommand('vscode.diff', uri, Uri.file(tempFilePath), 'diff')
307 | })
308 | .catch((err) => {
309 | console.error(err)
310 | })
311 | }
312 |
313 | doAutoFixByBracket(event: TextDocumentChangeEvent) {
314 | if (event.contentChanges.length == 0) return
315 | let pressedKey = event.contentChanges[0].text
316 | // console.log(pressedKey);
317 | if (!/^\s*\}$/.test(pressedKey)) {
318 | return
319 | }
320 |
321 | let editor = window.activeTextEditor
322 | let document = editor.document
323 | let originalStart = editor.selection.start
324 | commands.executeCommand('editor.action.jumpToBracket').then(() => {
325 | let start = editor.selection.start
326 | let offsetStart0 = document.offsetAt(originalStart)
327 | let offsetStart1 = document.offsetAt(start)
328 | if (offsetStart0 == offsetStart1) {
329 | return
330 | }
331 |
332 | let nextChar = document.getText(new Range(start, start.translate(0, 1)))
333 | if (offsetStart0 - offsetStart1 < 3 || nextChar != '{') {
334 | // jumpToBracket to wrong match bracket, do nothing
335 | commands.executeCommand('cursorUndo')
336 | return
337 | }
338 |
339 | let line = document.lineAt(start)
340 | let code = ' fixed.replace(/^<\?php[\s\S]+?\$__pcf__spliter\s*=\s*0;\r?\n/, '').replace(/\s*$/, '')
342 | let searchIndex = -1
343 | if (/^\s*\{\s*$/.test(line.text)) {
344 | // check previous line
345 | let preline = document.lineAt(line.lineNumber - 1)
346 | searchIndex = preline.text.search(/((if|for|foreach|while|switch|^\s*function\s+\w+|^\s*function\s*)\s*\(.+?\)|(class|trait|interface)\s+[\w ]+|do|try)\s*$/i)
347 | if (searchIndex > -1) {
348 | line = preline
349 | }
350 | } else {
351 | searchIndex = line.text.search(/((if|for|foreach|while|switch|^\s*function\s+\w+|^\s*function\s*)\s*\(.+?\)|(class|trait|interface)\s+[\w ]+|do|try)\s*\{\s*$/i)
352 | }
353 |
354 | if (searchIndex > -1) {
355 | start = line.range.start
356 | } else {
357 | // indent + if(1)
358 | code += line.text.match(/^(\s*)\S+/)[1] + 'if(1)'
359 | dealFun = (fixed) => {
360 | let match = fixed.match(/^<\?php[\s\S]+?\$__pcf__spliter\s*=\s*0;\s+?if\s*\(\s*1\s*\)\s*(\{[\s\S]+?\})\s*$/i)
361 | if (match != null) {
362 | fixed = match[1]
363 | } else {
364 | fixed = ''
365 | }
366 | return fixed
367 | }
368 | }
369 |
370 | commands.executeCommand('cursorUndo').then(() => {
371 | let end = editor.selection.start
372 | let range = new Range(start, end)
373 | let originalText = code + document.getText(range)
374 |
375 | this.format(originalText, document.uri, false, true)
376 | .then((text) => {
377 | text = dealFun(text)
378 | if (text != dealFun(originalText)) {
379 | editor
380 | .edit((builder) => {
381 | builder.replace(range, text)
382 | })
383 | .then(() => {
384 | if (editor.selections.length > 0) {
385 | commands.executeCommand('cancelSelection')
386 | }
387 | })
388 | }
389 | })
390 | .catch((err) => {
391 | console.log(err)
392 | })
393 | })
394 | })
395 | }
396 |
397 | doAutoFixBySemicolon(event: TextDocumentChangeEvent) {
398 | if (event.contentChanges.length == 0) return
399 | let pressedKey = event.contentChanges[0].text
400 | // console.log(pressedKey);
401 | if (pressedKey != ';') {
402 | return
403 | }
404 | let editor = window.activeTextEditor
405 | let line = editor.document.lineAt(editor.selection.start)
406 | if (line.text.length < 5) {
407 | return
408 | }
409 | // only at last char
410 | if (line.range.end.character != editor.selection.end.character + 1) {
411 | return
412 | }
413 |
414 | let indent = line.text.match(/^(\s*)/)[1]
415 | let dealFun = (fixed) => fixed.replace(/^<\?php[\s\S]+?\$__pcf__spliter\s*=\s*0;\r?\n/, '').replace(/\s+$/, '')
416 | let range = line.range
417 | let originalText = ' {
421 | text = dealFun(text)
422 | if (text != dealFun(originalText)) {
423 | text = indent + text
424 | editor
425 | .edit((builder) => {
426 | builder.replace(range, text)
427 | })
428 | .then(() => {
429 | if (editor.selections.length > 0) {
430 | commands.executeCommand('cancelSelection')
431 | }
432 | })
433 | }
434 | })
435 | .catch((err) => {
436 | console.log(err)
437 | })
438 | }
439 |
440 | formattingProvider(document: TextDocument, options: FormattingOptions): Promise {
441 | if (this.isExcluded(document)) {
442 | return
443 | }
444 |
445 | // only activeTextEditor, or last activeTextEditor
446 | // if (window.activeTextEditor == undefined
447 | // || (window.activeTextEditor.document.uri.toString() != document.uri.toString() && lastActiveEditor != document.uri.toString()))
448 | // return
449 |
450 | isRunning = false
451 | return new Promise((resolve, reject) => {
452 | let originalText = document.getText()
453 | let lastLine = document.lineAt(document.lineCount - 1)
454 | let range = new Range(new Position(0, 0), lastLine.range.end)
455 | let htmlOptions = Object.assign(options, workspace.getConfiguration('html').get('format'))
456 | let originalText2 = this.formatHtml ? beautify(originalText, htmlOptions) : originalText
457 |
458 | this.format(originalText2, document.uri)
459 | .then((text) => {
460 | if (text && text != originalText) {
461 | resolve([new TextEdit(range, text)])
462 | } else {
463 | resolve([])
464 | }
465 | })
466 | .catch((err) => {
467 | console.log(err)
468 | reject()
469 | })
470 | })
471 | }
472 |
473 | rangeFormattingProvider(document: TextDocument, range: Range): Promise {
474 | if (this.isExcluded(document)) {
475 | return
476 | }
477 |
478 | // only activeTextEditor, or last activeTextEditor
479 | // if (window.activeTextEditor == undefined
480 | // || (window.activeTextEditor.document.uri.toString() != document.uri.toString() && lastActiveEditor != document.uri.toString()))
481 | // return
482 |
483 | isRunning = false
484 | return new Promise((resolve, reject) => {
485 | let originalText = document.getText(range)
486 | if (originalText.replace(/\s+/g, '').length == 0) {
487 | reject()
488 | return
489 | }
490 | let addPHPTag = false
491 | if (originalText.search(/^\s*<\?php/i) == -1) {
492 | originalText = ' {
498 | if (addPHPTag) {
499 | text = text.replace(/^<\?php\r?\n/, '')
500 | }
501 | if (text && text != originalText) {
502 | resolve([new TextEdit(range, text)])
503 | } else {
504 | resolve([])
505 | }
506 | })
507 | .catch((err) => {
508 | console.log(err)
509 | reject()
510 | })
511 | })
512 | }
513 |
514 | isExcluded(document: TextDocument): boolean {
515 | if (this.exclude.length > 0 && document.uri.scheme == 'file' && !document.isUntitled) {
516 | return anymatch(this.exclude, document.uri.path)
517 | }
518 | return false
519 | }
520 |
521 | errorTip() {
522 | window.showErrorMessage('PHP CS Fixer: executablePath not found. Try setting `"php-cs-fixer.executablePath": "${extensionPath}/php-cs-fixer.phar"` and try again.', 'Open Output').then((t) => {
523 | if (t == 'Open Output') {
524 | showOutput()
525 | }
526 | })
527 | // const config = workspace.getConfiguration('php-cs-fixer')
528 | // config.update('executablePath', '${extensionPath}/php-cs-fixer.phar', true)
529 | }
530 |
531 | checkUpdate() {
532 | setTimeout(() => {
533 | let config = workspace.getConfiguration('php-cs-fixer')
534 | let executablePath = config.get('executablePath', 'php-cs-fixer')
535 | let lastDownload = config.get('lastDownload', 1)
536 | if (lastDownload !== 0 && executablePath == '${extensionPath}/php-cs-fixer.phar' && lastDownload + 1000 * 3600 * 24 * 7 < new Date().getTime()) {
537 | console.log('php-cs-fixer: check for updating...')
538 | const { DownloaderHelper } = require('node-downloader-helper')
539 | let dl = new DownloaderHelper('https://cs.symfony.com/download/php-cs-fixer-v3.phar', __dirname, { fileName: 'php-cs-fixer.phar.tmp', override: true })
540 | dl.on('end', () => {
541 | fs.unlinkSync(path.join(__dirname, 'php-cs-fixer.phar'))
542 | fs.renameSync(path.join(__dirname, 'php-cs-fixer.phar.tmp'), path.join(__dirname, 'php-cs-fixer.phar'))
543 | config.update('lastDownload', new Date().getTime(), true)
544 | })
545 | dl.start()
546 | }
547 | }, 1000 * 60)
548 | }
549 | }
550 |
551 | exports.activate = (context: ExtensionContext) => {
552 | const pcf = new PHPCSFixer()
553 |
554 | // context.subscriptions.push(window.onDidChangeActiveTextEditor(te => {
555 | // if (pcf.fileAutoSave != 'off') {
556 | // setTimeout(() => lastActiveEditor = te == undefined ? undefined : te.document.uri.toString(), pcf.fileAutoSaveDelay + 100)
557 | // }
558 | // }))
559 |
560 | context.subscriptions.push(
561 | workspace.onWillSaveTextDocument((event) => {
562 | if (event.document.languageId == 'php' && pcf.onsave && pcf.editorFormatOnSave == false) {
563 | event.waitUntil(pcf.formattingProvider(event.document, {} as any))
564 | }
565 | })
566 | )
567 |
568 | context.subscriptions.push(
569 | commands.registerTextEditorCommand('php-cs-fixer.fix', (textEditor) => {
570 | if (textEditor.document.languageId == 'php') {
571 | pcf.formattingProvider(textEditor.document, {} as any).then((tes) => {
572 | if (tes && tes.length > 0) {
573 | textEditor.edit((eb) => {
574 | eb.replace(tes[0].range, tes[0].newText)
575 | })
576 | }
577 | })
578 | }
579 | })
580 | )
581 |
582 | context.subscriptions.push(
583 | workspace.onDidChangeTextDocument((event) => {
584 | if (event.document.languageId == 'php' && isRunning == false) {
585 | if (pcf.isExcluded(event.document)) {
586 | return
587 | }
588 |
589 | if (pcf.autoFixByBracket) {
590 | pcf.doAutoFixByBracket(event)
591 | }
592 | if (pcf.autoFixBySemicolon) {
593 | pcf.doAutoFixBySemicolon(event)
594 | }
595 | }
596 | })
597 | )
598 |
599 | context.subscriptions.push(
600 | workspace.onDidChangeConfiguration(() => {
601 | pcf.loadSettings()
602 | })
603 | )
604 |
605 | if (pcf.documentFormattingProvider) {
606 | context.subscriptions.push(
607 | languages.registerDocumentFormattingEditProvider('php', {
608 | provideDocumentFormattingEdits: (document, options, token) => {
609 | return pcf.formattingProvider(document, options)
610 | },
611 | })
612 | )
613 |
614 | context.subscriptions.push(
615 | languages.registerDocumentRangeFormattingEditProvider('php', {
616 | provideDocumentRangeFormattingEdits: (document, range, options, token) => {
617 | return pcf.rangeFormattingProvider(document, range)
618 | },
619 | })
620 | )
621 | }
622 |
623 | context.subscriptions.push(
624 | commands.registerCommand('php-cs-fixer.fix2', (f) => {
625 | if (f == undefined) {
626 | let editor = window.activeTextEditor
627 | if (editor != undefined && editor.document.languageId == 'php') {
628 | f = editor.document.uri
629 | }
630 | }
631 | if (f && f.scheme == 'file') {
632 | let stat = fs.statSync(f.fsPath)
633 | if (stat.isDirectory()) {
634 | showOutput()
635 | }
636 | if (f != undefined) {
637 | pcf.fix(f)
638 | }
639 | }
640 | })
641 | )
642 |
643 | context.subscriptions.push(
644 | commands.registerCommand('php-cs-fixer.diff', (f) => {
645 | if (f == undefined) {
646 | let editor = window.activeTextEditor
647 | if (editor != undefined && editor.document.languageId == 'php') {
648 | f = editor.document.uri
649 | }
650 | }
651 | if (f && f.scheme == 'file') {
652 | if (f != undefined) {
653 | pcf.diff(f)
654 | }
655 | }
656 | })
657 | )
658 |
659 | context.subscriptions.push(commands.registerCommand('php-cs-fixer.showOutput', showOutput))
660 | }
661 |
662 | exports.deactivate = () => {
663 | disposeOutput()
664 | }
665 |
--------------------------------------------------------------------------------