Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit fce1bab

Browse files
author
Akos Kitta
committed
Use clang-format as the default sketch formatter.
- Bumped `clangd` to `14.0.0`, - Can use `.clang-format` from: - current sketch folder, - `~/.arduinoIDE/.clang-format`, - `directories#data/.clang-format`, or - falls back to default formatter styles. Closes #1009 Closes #566 Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
1 parent d780961 commit fce1bab

File tree

9 files changed

+461
-3
lines changed

9 files changed

+461
-3
lines changed

‎arduino-ide-extension/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@
163163
"version": "2.0.0"
164164
},
165165
"clangd": {
166-
"version": "13.0.0"
166+
"version": "14.0.0"
167167
},
168168
"languageServer": {
169169
"version": "0.6.0"

‎arduino-ide-extension/scripts/download-ls.js‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,21 +66,24 @@
6666
build,
6767
`arduino-language-server${platform === 'win32' ? '.exe' : ''}`
6868
);
69-
let clangdExecutablePath, lsSuffix, clangdSuffix;
69+
let clangdExecutablePath, clangFormatExecutablePath,lsSuffix, clangdSuffix;
7070

7171
switch (platformArch) {
7272
case 'darwin-x64':
7373
clangdExecutablePath = path.join(build, 'clangd');
74+
clangFormatExecutablePath = path.join(build, 'clang-format');
7475
lsSuffix = 'macOS_64bit.tar.gz';
7576
clangdSuffix = 'macOS_64bit';
7677
break;
7778
case 'linux-x64':
7879
clangdExecutablePath = path.join(build, 'clangd');
80+
clangFormatExecutablePath = path.join(build, 'clang-format');
7981
lsSuffix = 'Linux_64bit.tar.gz';
8082
clangdSuffix = 'Linux_64bit';
8183
break;
8284
case 'win32-x64':
8385
clangdExecutablePath = path.join(build, 'clangd.exe');
86+
clangFormatExecutablePath = path.join(build, 'clang-format.exe');
8487
lsSuffix = 'Windows_64bit.zip';
8588
clangdSuffix = 'Windows_64bit';
8689
break;
@@ -103,4 +106,15 @@
103106
downloader.downloadUnzipAll(clangdUrl, build, clangdExecutablePath, force, {
104107
strip: 1,
105108
}); // `strip`: the new clangd (12.x) is zipped into a folder, so we have to strip the outmost folder.
109+
110+
const clangdFormatUrl = `https://downloads.arduino.cc/tools/clang-format_${clangdVersion}_${clangdSuffix}.tar.bz2`;
111+
downloader.downloadUnzipAll(
112+
clangdFormatUrl,
113+
build,
114+
clangFormatExecutablePath,
115+
force,
116+
{
117+
strip: 1,
118+
}
119+
);
106120
})();

‎arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,10 @@ import { EditorManager } from './theia/editor/editor-manager';
280280
import { HostedPluginEvents } from './hosted-plugin-events';
281281
import { HostedPluginSupport } from './theia/plugin-ext/hosted-plugin';
282282
import { HostedPluginSupport as TheiaHostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin';
283+
import { Formatter, FormatterPath } from '../common/protocol/formatter';
284+
import { Format } from './contributions/format';
285+
import { MonacoFormattingConflictsContribution } from './theia/monaco/monaco-formatting-conflicts';
286+
import { MonacoFormattingConflictsContribution as TheiaMonacoFormattingConflictsContribution } from '@theia/monaco/lib/browser/monaco-formatting-conflicts';
283287

284288
const ElementQueries = require('css-element-queries/src/ElementQueries');
285289

@@ -566,6 +570,12 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
566570
)
567571
.inSingletonScope();
568572

573+
bind(Formatter)
574+
.toDynamicValue(({ container }) =>
575+
WebSocketConnectionProvider.createProxy(container, FormatterPath)
576+
)
577+
.inSingletonScope();
578+
569579
bind(ArduinoFirmwareUploader)
570580
.toDynamicValue((context) =>
571581
WebSocketConnectionProvider.createProxy(
@@ -633,6 +643,14 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
633643
Contribution.configure(bind, ArchiveSketch);
634644
Contribution.configure(bind, AddZipLibrary);
635645
Contribution.configure(bind, PlotterFrontendContribution);
646+
Contribution.configure(bind, Format);
647+
648+
// Disabled the quick-pick customization from Theia when multiple formatters are available.
649+
// Use the default VS Code behavior, and pick the first one. In the IDE2, clang-format has `exclusive` selectors.
650+
bind(MonacoFormattingConflictsContribution).toSelf().inSingletonScope();
651+
rebind(TheiaMonacoFormattingConflictsContribution).toService(
652+
MonacoFormattingConflictsContribution
653+
);
636654

637655
bind(ResponseServiceImpl)
638656
.toSelf()
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { MaybePromise } from '@theia/core';
2+
import { inject, injectable } from '@theia/core/shared/inversify';
3+
import * as monaco from '@theia/monaco-editor-core';
4+
import { Formatter } from '../../common/protocol/formatter';
5+
import { Contribution, URI } from './contribution';
6+
7+
@injectable()
8+
export class Format
9+
extends Contribution
10+
implements
11+
monaco.languages.DocumentRangeFormattingEditProvider,
12+
monaco.languages.DocumentFormattingEditProvider
13+
{
14+
@inject(Formatter)
15+
private readonly formatter: Formatter;
16+
17+
override onStart(): MaybePromise<void> {
18+
const selector = this.selectorOf('ino', 'c', 'cpp', 'h', 'hpp', 'pde');
19+
monaco.languages.registerDocumentRangeFormattingEditProvider(
20+
selector,
21+
this
22+
);
23+
monaco.languages.registerDocumentFormattingEditProvider(selector, this);
24+
}
25+
async provideDocumentRangeFormattingEdits(
26+
model: monaco.editor.ITextModel,
27+
range: monaco.Range,
28+
options: monaco.languages.FormattingOptions,
29+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
30+
_token: monaco.CancellationToken
31+
): Promise<monaco.languages.TextEdit[]> {
32+
const text = await this.format(model, range, options);
33+
return [{ range, text }];
34+
}
35+
36+
async provideDocumentFormattingEdits(
37+
model: monaco.editor.ITextModel,
38+
options: monaco.languages.FormattingOptions,
39+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
40+
_token: monaco.CancellationToken
41+
): Promise<monaco.languages.TextEdit[]> {
42+
const range = this.fullRange(model);
43+
const text = await this.format(model, range, options);
44+
return [{ range, text }];
45+
}
46+
47+
private fullRange(model: monaco.editor.ITextModel): monaco.Range {
48+
const lastLine = model.getLineCount();
49+
const lastLineMaxColumn = model.getLineMaxColumn(lastLine);
50+
const end = new monaco.Position(lastLine, lastLineMaxColumn);
51+
return monaco.Range.fromPositions(new monaco.Position(1, 1), end);
52+
}
53+
54+
/**
55+
* From the currently opened workspaces (IDE2 has always one), it calculates all possible
56+
* folder locations where the `.clang-format` file could be.
57+
*/
58+
private formatterConfigFolderUris(model: monaco.editor.ITextModel): string[] {
59+
const editorUri = new URI(model.uri.toString());
60+
return this.workspaceService
61+
.tryGetRoots()
62+
.map(({ resource }) => resource)
63+
.filter((workspaceUri) => workspaceUri.isEqualOrParent(editorUri))
64+
.map((uri) => uri.toString());
65+
}
66+
67+
private format(
68+
model: monaco.editor.ITextModel,
69+
range: monaco.Range,
70+
options: monaco.languages.FormattingOptions
71+
): Promise<string> {
72+
console.info(
73+
`Formatting ${model.uri.toString()} [Range: ${JSON.stringify(
74+
range.toJSON()
75+
)}]`
76+
);
77+
const content = model.getValueInRange(range);
78+
const formatterConfigFolderUris = this.formatterConfigFolderUris(model);
79+
return this.formatter.format({
80+
content,
81+
formatterConfigFolderUris,
82+
options,
83+
});
84+
}
85+
86+
private selectorOf(
87+
...languageId: string[]
88+
): monaco.languages.LanguageSelector {
89+
return languageId.map((language) => ({
90+
language,
91+
exclusive: true, // <-- this should make sure the custom formatter has higher precedence over the LS formatter.
92+
}));
93+
}
94+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { injectable } from '@theia/core/shared/inversify';
2+
import { MonacoFormattingConflictsContribution as TheiaMonacoFormattingConflictsContribution } from '@theia/monaco/lib/browser/monaco-formatting-conflicts';
3+
4+
@injectable()
5+
export class MonacoFormattingConflictsContribution extends TheiaMonacoFormattingConflictsContribution {
6+
override async initialize(): Promise<void> {
7+
// NOOP - does not register a custom formatting conflicts selects.
8+
// Does not get and set formatter preferences when selecting from multiple formatters.
9+
// Does not show quick-pick input when multiple formatters are available for the text model.
10+
// Uses the default behavior from VS Code: https://github.com/microsoft/vscode/blob/fb9f488e51af2e2efe95a34f24ca11e1b2a3f744/src/vs/editor/editor.api.ts#L19-L21
11+
}
12+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
export const FormatterPath = '/services/formatter';
2+
export const Formatter = Symbol('Formatter');
3+
export interface Formatter {
4+
format({
5+
content,
6+
formatterConfigFolderUris,
7+
options,
8+
}: {
9+
content: string;
10+
formatterConfigFolderUris: string[];
11+
options?: FormatterOptions;
12+
}): Promise<string>;
13+
}
14+
export interface FormatterOptions {
15+
/**
16+
* Size of a tab in spaces.
17+
*/
18+
tabSize: number;
19+
/**
20+
* Prefer spaces over tabs.
21+
*/
22+
insertSpaces: boolean;
23+
}

‎arduino-ide-extension/src/node/arduino-ide-backend-module.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ import WebSocketServiceImpl from './web-socket/web-socket-service-impl';
9494
import { WebSocketService } from './web-socket/web-socket-service';
9595
import { ArduinoLocalizationContribution } from './arduino-localization-contribution';
9696
import { LocalizationContribution } from '@theia/core/lib/node/i18n/localization-contribution';
97+
import { ClangFormatter } from './clang-formatter';
98+
import { FormatterPath } from '../common/protocol/formatter';
9799

98100
export default new ContainerModule((bind, unbind, isBound, rebind) => {
99101
bind(BackendApplication).toSelf().inSingletonScope();
@@ -126,6 +128,17 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
126128
)
127129
.inSingletonScope();
128130

131+
// Shared formatter
132+
bind(ClangFormatter).toSelf().inSingletonScope();
133+
bind(ConnectionHandler)
134+
.toDynamicValue(
135+
({ container }) =>
136+
new JsonRpcConnectionHandler(FormatterPath, () =>
137+
container.get(ClangFormatter)
138+
)
139+
)
140+
.inSingletonScope();
141+
129142
// Examples service. One per backend, each connected FE gets a proxy.
130143
bind(ConnectionContainerModule).toConstantValue(
131144
ConnectionContainerModule.create(({ bind, bindBackendService }) => {

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /