|
1 | 1 | import { spawn } from 'child_process'
|
| 2 | +import * as editorconfig from 'editorconfig' |
2 | 3 | import * as LSP from 'vscode-languageserver/node'
|
3 |
| -import { TextDocument, TextEdit } from 'vscode-languageserver-textdocument' |
| 4 | +import { DocumentUri, TextDocument, TextEdit } from 'vscode-languageserver-textdocument' |
4 | 5 |
|
5 | 6 | import { logger } from '../util/logger'
|
6 | 7 |
|
@@ -58,26 +59,82 @@ export class Formatter {
|
58 | 59 | ]
|
59 | 60 | }
|
60 | 61 |
|
| 62 | + private async getShfmtArguments( |
| 63 | + documentUri: DocumentUri, |
| 64 | + formatOptions?: LSP.FormattingOptions | null, |
| 65 | + lspShfmtConfig?: Record<string, string | boolean> | null, |
| 66 | + ): Promise<string[]> { |
| 67 | + const args: string[] = [] |
| 68 | + |
| 69 | + // this is the config that we'll use to build args - default to language server config |
| 70 | + let activeShfmtConfig = { ...lspShfmtConfig } |
| 71 | + |
| 72 | + // do we have a document stored on the local filesystem? |
| 73 | + const filepathMatch = documentUri.match(/^file:\/\/(.*)$/) |
| 74 | + if (filepathMatch) { |
| 75 | + const filepath = filepathMatch[1] |
| 76 | + args.push(`--filename=${filepathMatch[1]}`) |
| 77 | + |
| 78 | + const editorconfigProperties = await editorconfig.parse(filepath) |
| 79 | + logger.debug( |
| 80 | + `Shfmt: found .editorconfig properties: ${JSON.stringify( |
| 81 | + editorconfigProperties, |
| 82 | + )}`, |
| 83 | + ) |
| 84 | + |
| 85 | + const editorconfigShfmtConfig: Record<string, any> = {} |
| 86 | + editorconfigShfmtConfig.binaryNextLine = editorconfigProperties.binary_next_line |
| 87 | + editorconfigShfmtConfig.caseIndent = editorconfigProperties.switch_case_indent |
| 88 | + editorconfigShfmtConfig.funcNextLine = editorconfigProperties.function_next_line |
| 89 | + editorconfigShfmtConfig.keepPadding = editorconfigProperties.keep_padding |
| 90 | + // --simplify is not supported via .editorconfig |
| 91 | + editorconfigShfmtConfig.spaceRedirects = editorconfigProperties.space_redirects |
| 92 | + editorconfigShfmtConfig.languageDialect = editorconfigProperties.shell_variant |
| 93 | + |
| 94 | + // if we have any shfmt-specific options in .editorconfig, use the config in .editorconfig and |
| 95 | + // ignore the language server config (this is similar to shfmt's approach of using either |
| 96 | + // .editorconfig or command line flags, but not both) |
| 97 | + if ( |
| 98 | + editorconfigShfmtConfig.binaryNextLine !== undefined || |
| 99 | + editorconfigShfmtConfig.caseIndent !== undefined || |
| 100 | + editorconfigShfmtConfig.funcNextLine !== undefined || |
| 101 | + editorconfigShfmtConfig.keepPadding !== undefined || |
| 102 | + editorconfigShfmtConfig.spaceRedirects !== undefined || |
| 103 | + editorconfigShfmtConfig.languageDialect !== undefined |
| 104 | + ) { |
| 105 | + logger.debug( |
| 106 | + 'Shfmt: detected shfmt properties in .editorconfig - ignoring language server shfmt config', |
| 107 | + ) |
| 108 | + activeShfmtConfig = { ...editorconfigShfmtConfig } |
| 109 | + } else { |
| 110 | + logger.debug( |
| 111 | + 'Shfmt: no shfmt properties found in .editorconfig - using language server shfmt config', |
| 112 | + ) |
| 113 | + } |
| 114 | + } |
| 115 | + |
| 116 | + // indentation always comes via the editor - if someone is using .editorconfig then the |
| 117 | + // expectation is that they will have configured their editor's indentation in this way too |
| 118 | + const indentation: number = formatOptions?.insertSpaces ? formatOptions.tabSize : 0 |
| 119 | + args.push(`-i=${indentation}`) // --indent |
| 120 | + |
| 121 | + if (activeShfmtConfig?.binaryNextLine) args.push('-bn') // --binary-next-line |
| 122 | + if (activeShfmtConfig?.caseIndent) args.push('-ci') // --case-indent |
| 123 | + if (activeShfmtConfig?.funcNextLine) args.push('-fn') // --func-next-line |
| 124 | + if (activeShfmtConfig?.keepPadding) args.push('-kp') // --keep-padding |
| 125 | + if (activeShfmtConfig?.simplifyCode) args.push('-s') // --simplify |
| 126 | + if (activeShfmtConfig?.spaceRedirects) args.push('-sr') // --space-redirects |
| 127 | + if (activeShfmtConfig?.languageDialect) args.push(`-ln=${activeShfmtConfig.languageDialect}`) // --language-dialect |
| 128 | + |
| 129 | + return args |
| 130 | + } |
| 131 | + |
61 | 132 | private async runShfmt(
|
62 | 133 | document: TextDocument,
|
63 | 134 | formatOptions?: LSP.FormattingOptions | null,
|
64 | 135 | shfmtConfig?: Record<string, string | boolean> | null,
|
65 | 136 | ): Promise<string> {
|
66 |
| - const indentation: number = formatOptions?.insertSpaces ? formatOptions.tabSize : 0 |
67 |
| - const args: string[] = [`-i=${indentation}`] // --indent |
68 |
| - if (shfmtConfig?.binaryNextLine) args.push('-bn') // --binary-next-line |
69 |
| - if (shfmtConfig?.caseIndent) args.push('-ci') // --case-indent |
70 |
| - if (shfmtConfig?.funcNextLine) args.push('-fn') // --func-next-line |
71 |
| - if (shfmtConfig?.keepPadding) args.push('-kp') // --keep-padding |
72 |
| - if (shfmtConfig?.simplifyCode) args.push('-s') // --simplify |
73 |
| - if (shfmtConfig?.spaceRedirects) args.push('-sr') // --space-redirects |
74 |
| - if (shfmtConfig?.languageDialect) args.push(`-ln=${shfmtConfig.languageDialect}`) // --language-dialect |
75 |
| - |
76 |
| - // If we can determine a local filename, pass that to shfmt to aid language dialect detection |
77 |
| - const filePathMatch = document.uri.match(/^file:\/\/(.*)$/) |
78 |
| - if (filePathMatch) { |
79 |
| - args.push(`--filename=${filePathMatch[1]}`) |
80 |
| - } |
| 137 | + const args = await this.getShfmtArguments(document.uri, formatOptions, shfmtConfig) |
81 | 138 |
|
82 | 139 | logger.debug(`Shfmt: running "${this.executablePath} ${args.join(' ')}"`)
|
83 | 140 |
|
|
0 commit comments