mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-06-24 21:16:34 +00:00

The Arduino IDE's "Auto Format" feature is configured to produce the standard Arduino sketch formatting style by default. The Arduino IDE editor's default settings are compliant with that style. However, the user may adjust the editor settings. In this case, the Arduino IDE automatically adjusts the Auto Format configuration to align with the user's preferences. The formatter configuration is consumed by several other projects in addition to the Arduino IDE. For this reason, the configuration is hosted and maintained in a centralized location, from which it is pulled by all projects that use it. Previously, the adjustment of the Arduino IDE formatter configuration according to the editor settings was integrated into the configuration object itself. This meant that the standardized configuration had to be modified each time it was pulled in to sync from the upstream source. Moving the base formatter configuration object to a dedicated file, separated from the handling and adjustment code allows syncs to be done by simply replacing the existing configuration file with the one automatically generated by the CI system of the repository where the source configuration is hosted.
157 lines
4.6 KiB
TypeScript
157 lines
4.6 KiB
TypeScript
import * as os from 'os';
|
|
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
|
|
import { MaybePromise } from '@theia/core/lib/common/types';
|
|
import { FileUri } from '@theia/core/lib/node/file-uri';
|
|
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
import { constants, promises as fs } from 'fs';
|
|
import { join } from 'path';
|
|
import { ConfigService } from '../common/protocol';
|
|
import { Formatter, FormatterOptions } from '../common/protocol/formatter';
|
|
import { getExecPath, spawnCommand } from './exec-util';
|
|
|
|
@injectable()
|
|
export class ClangFormatter implements Formatter {
|
|
@inject(ConfigService)
|
|
private readonly configService: ConfigService;
|
|
|
|
@inject(EnvVariablesServer)
|
|
private readonly envVariableServer: EnvVariablesServer;
|
|
|
|
async format({
|
|
content,
|
|
formatterConfigFolderUris,
|
|
options,
|
|
}: {
|
|
content: string;
|
|
formatterConfigFolderUris: string[];
|
|
options?: FormatterOptions;
|
|
}): Promise<string> {
|
|
const [execPath, style] = await Promise.all([
|
|
this.execPath(),
|
|
this.style(formatterConfigFolderUris, options),
|
|
]);
|
|
const formatted = await spawnCommand(
|
|
`"${execPath}"`,
|
|
[style],
|
|
console.error,
|
|
content
|
|
);
|
|
return formatted;
|
|
}
|
|
|
|
private _execPath: string | undefined;
|
|
private async execPath(): Promise<string> {
|
|
if (this._execPath) {
|
|
return this._execPath;
|
|
}
|
|
this._execPath = await getExecPath('clang-format');
|
|
return this._execPath;
|
|
}
|
|
|
|
/**
|
|
* Calculates the `-style` flag for the formatter. Uses a `.clang-format` file if exists.
|
|
* Otherwise, falls back to the default config.
|
|
*
|
|
* Style precedence:
|
|
* 1. in the sketch folder,
|
|
* 1. `~/.arduinoIDE/.clang-format`,
|
|
* 1. `directories#data/.clang-format`, and
|
|
* 1. default style flag as a string.
|
|
*
|
|
* See: https://github.com/arduino/arduino-ide/issues/566
|
|
*/
|
|
private async style(
|
|
formatterConfigFolderUris: string[],
|
|
options?: FormatterOptions
|
|
): Promise<string> {
|
|
const clangFormatPaths = await Promise.all([
|
|
...formatterConfigFolderUris.map((uri) => this.clangConfigPath(uri)),
|
|
this.clangConfigPath(this.configDirPath()),
|
|
this.clangConfigPath(this.dataDirPath()),
|
|
]);
|
|
const first = clangFormatPaths.filter(Boolean).shift();
|
|
if (first) {
|
|
console.debug(
|
|
`Using ${ClangFormatFile} style configuration from '${first}'.`
|
|
);
|
|
return `-style=file:"${first}"`;
|
|
}
|
|
return `-style="${style(toClangOptions(options))}"`;
|
|
}
|
|
|
|
private async dataDirPath(): Promise<string> {
|
|
const { dataDirUri } = await this.configService.getConfiguration();
|
|
return FileUri.fsPath(dataDirUri);
|
|
}
|
|
|
|
private async configDirPath(): Promise<string> {
|
|
const configDirUri = await this.envVariableServer.getConfigDirUri();
|
|
return FileUri.fsPath(configDirUri);
|
|
}
|
|
|
|
private async clangConfigPath(
|
|
folderUri: MaybePromise<string>
|
|
): Promise<string | undefined> {
|
|
const folderPath = FileUri.fsPath(await folderUri);
|
|
const clangFormatPath = join(folderPath, ClangFormatFile);
|
|
try {
|
|
await fs.access(clangFormatPath, constants.R_OK);
|
|
return clangFormatPath;
|
|
} catch {
|
|
return undefined;
|
|
}
|
|
}
|
|
}
|
|
|
|
interface ClangFormatOptions {
|
|
readonly UseTab: 'Never' | 'ForIndentation';
|
|
readonly TabWidth: number;
|
|
}
|
|
|
|
const ClangFormatFile = '.clang-format';
|
|
|
|
function toClangOptions(
|
|
options?: FormatterOptions | undefined
|
|
): ClangFormatOptions {
|
|
if (!!options) {
|
|
return {
|
|
UseTab: options.insertSpaces ? 'Never' : 'ForIndentation',
|
|
TabWidth: options.tabSize,
|
|
};
|
|
}
|
|
return { UseTab: 'Never', TabWidth: 2 };
|
|
}
|
|
|
|
export function style({ TabWidth, UseTab }: ClangFormatOptions): string {
|
|
let styleArgument = JSON.stringify(styleJson({ TabWidth, UseTab })).replace(
|
|
/[\\"]/g,
|
|
'\\$&'
|
|
);
|
|
if (os.platform() === 'win32') {
|
|
// Windows command interpreter does not use backslash escapes. This causes the argument to have alternate quoted and
|
|
// unquoted sections.
|
|
// Special characters in the unquoted sections must be caret escaped.
|
|
const styleArgumentSplit = styleArgument.split('"');
|
|
for (let i = 1; i < styleArgumentSplit.length; i += 2) {
|
|
styleArgumentSplit[i] = styleArgumentSplit[i].replace(/[<>^|]/g, '^$&');
|
|
}
|
|
|
|
styleArgument = styleArgumentSplit.join('"');
|
|
}
|
|
|
|
return styleArgument;
|
|
}
|
|
|
|
function styleJson({
|
|
TabWidth,
|
|
UseTab,
|
|
}: ClangFormatOptions): Record<string, unknown> {
|
|
// Source: https://github.com/arduino/tooling-project-assets/tree/main/other/clang-format-configuration
|
|
const defaultConfig = require('../../src/node/default-formatter-config.json');
|
|
return {
|
|
...defaultConfig,
|
|
TabWidth,
|
|
UseTab,
|
|
};
|
|
}
|